diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js index 7909c0be5..4e540a259 100644 --- a/lib/selectors/socket-selectors.js +++ b/lib/selectors/socket-selectors.js @@ -1,252 +1,261 @@ // @flow import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; import { updatesCurrentAsOfSelector, currentAsOfSelector, urlPrefixSelector, cookieSelector, } from './keyserver-selectors.js'; import { currentCalendarQuery } from './nav-selectors.js'; import { createOpenSocketFunction } from '../shared/socket-utils.js'; import type { BoundStateSyncSpec } from '../shared/state-sync/state-sync-spec.js'; import { stateSyncSpecs } from '../shared/state-sync/state-sync-specs.js'; import threadWatcher from '../shared/thread-watcher.js'; import type { SignedIdentityKeysBlob } from '../types/crypto-types.js'; import { type CalendarQuery } from '../types/entry-types.js'; import type { AppState } from '../types/redux-types.js'; import type { ClientReportCreationRequest } from '../types/report-types.js'; import { serverRequestTypes, type ClientServerRequest, type ClientClientResponse, } from '../types/request-types.js'; import type { SessionState } from '../types/session-types.js'; import { getConfig } from '../utils/config.js'; import { values } from '../utils/objects.js'; const baseOpenSocketSelector: ( keyserverID: string, ) => (state: AppState) => ?() => WebSocket = keyserverID => createSelector( urlPrefixSelector(keyserverID), // We don't actually use the cookie in the socket open function, // but we do use it in the initial message, and when the cookie changes // the socket needs to be reopened. By including the cookie here, // whenever the cookie changes this function will change, // which tells the Socket component to restart the connection. cookieSelector(keyserverID), (urlPrefix: ?string) => { if (!urlPrefix) { return null; } return createOpenSocketFunction(urlPrefix); }, ); const openSocketSelector: ( keyserverID: string, ) => (state: AppState) => ?() => WebSocket = _memoize(baseOpenSocketSelector); const queuedReports: ( state: AppState, ) => $ReadOnlyArray = createSelector( (state: AppState) => state.reportStore.queuedReports, ( mainQueuedReports: $ReadOnlyArray, ): $ReadOnlyArray => mainQueuedReports, ); // We pass all selectors specified in stateSyncSpecs and get the resulting // BoundStateSyncSpecs in the specs array. We do it so we don't have to // modify the selector when we add a new spec. type BoundStateSyncSpecs = { +specsPerHashKey: { +[string]: BoundStateSyncSpec }, +specPerInnerHashKey: { +[string]: BoundStateSyncSpec }, }; const stateSyncSpecSelectors = values(stateSyncSpecs).map( spec => spec.selector, ); const boundStateSyncSpecsSelector: AppState => BoundStateSyncSpecs = // The FlowFixMe is needed because createSelector types require flow // to know the number of subselectors at compile time. // $FlowFixMe createSelector(stateSyncSpecSelectors, (...specs) => { const boundSpecs = (specs: BoundStateSyncSpec[]); // We create a map from `hashKey` to a given spec for easier lookup later const specsPerHashKey = Object.fromEntries( boundSpecs.map(spec => [spec.hashKey, spec]), ); // We do the same for innerHashKey const specPerInnerHashKey = Object.fromEntries( boundSpecs .filter(spec => spec.innerHashSpec?.hashKey) .map(spec => [spec.innerHashSpec?.hashKey, spec]), ); return { specsPerHashKey, specPerInnerHashKey }; }); +async function getSignedIdentityKeysBlob(): Promise { + const { olmAPI } = getConfig(); + await olmAPI.initializeCryptoAccount(); + const { blobPayload, signature } = await olmAPI.getUserPublicKey(); + const signedIdentityKeysBlob: SignedIdentityKeysBlob = { + payload: blobPayload, + signature, + }; + return signedIdentityKeysBlob; +} + const getClientResponsesSelector: ( state: AppState, keyserverID: string, ) => ( calendarActive: boolean, - getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: () => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( boundStateSyncSpecsSelector, currentCalendarQuery, (state: AppState, keyserverID: string) => keyserverID, ( boundStateSyncSpecs: BoundStateSyncSpecs, calendarQuery: (calendarActive: boolean) => CalendarQuery, keyserverID: string, ) => { return async ( calendarActive: boolean, - getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: () => Promise, serverRequests: $ReadOnlyArray, ): Promise<$ReadOnlyArray> => { const clientResponses = []; const serverRequestedPlatformDetails = serverRequests.some( request => request.type === serverRequestTypes.PLATFORM_DETAILS, ); const { specsPerHashKey, specPerInnerHashKey } = boundStateSyncSpecs; for (const serverRequest of serverRequests) { if ( serverRequest.type === serverRequestTypes.PLATFORM && !serverRequestedPlatformDetails ) { clientResponses.push({ type: serverRequestTypes.PLATFORM, platform: getConfig().platformDetails.platform, }); } else if (serverRequest.type === serverRequestTypes.PLATFORM_DETAILS) { clientResponses.push({ type: serverRequestTypes.PLATFORM_DETAILS, platformDetails: getConfig().platformDetails, }); } else if (serverRequest.type === serverRequestTypes.CHECK_STATE) { const query = calendarQuery(calendarActive); const hashResults: { [string]: boolean } = {}; for (const key in serverRequest.hashesToCheck) { const expectedHashValue = serverRequest.hashesToCheck[key]; let hashValue; const [specKey, id] = key.split('|'); if (id) { hashValue = specPerInnerHashKey[specKey]?.getInfoHash( id, keyserverID, ); } else { hashValue = specsPerHashKey[specKey]?.getAllInfosHash( query, keyserverID, ); } // If hashValue values is null then we are still calculating // the hashes in the background. In this case we return true // to skip this state check. Future state checks (after the hash // calculation complete) will be handled normally. // Another case when this is null is when we are handling state // sync with a non-authoritative keyserver. We don't want to sync // user store and current user info with such keyservers. if (!hashValue) { hashResults[key] = true; } else { hashResults[key] = expectedHashValue === hashValue; } } const { failUnmentioned } = serverRequest; for (const spec of values(specPerInnerHashKey)) { const innerHashKey = spec.innerHashSpec?.hashKey; if (!failUnmentioned?.[spec.hashKey] || !innerHashKey) { continue; } const ids = spec.getIDs(query, keyserverID); if (!ids) { continue; } for (const id of ids) { const key = `${innerHashKey}|${id}`; const hashResult = hashResults[key]; if (hashResult === null || hashResult === undefined) { hashResults[key] = false; } } } clientResponses.push({ type: serverRequestTypes.CHECK_STATE, hashResults, }); } else if ( serverRequest.type === serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB ) { const signedIdentityKeysBlob = await getSignedIdentityKeysBlob(); clientResponses.push({ type: serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB, signedIdentityKeysBlob, }); } else if ( serverRequest.type === serverRequestTypes.INITIAL_NOTIFICATIONS_ENCRYPTED_MESSAGE ) { const initialNotificationsEncryptedMessage = await getInitialNotificationsEncryptedMessage(); clientResponses.push({ type: serverRequestTypes.INITIAL_NOTIFICATIONS_ENCRYPTED_MESSAGE, initialNotificationsEncryptedMessage, }); } } return clientResponses; }; }, ); const baseSessionStateFuncSelector: ( keyserverID: string, ) => ( state: AppState, ) => (calendarActive: boolean) => SessionState = keyserverID => createSelector( currentAsOfSelector(keyserverID), updatesCurrentAsOfSelector(keyserverID), currentCalendarQuery, ( messagesCurrentAsOf: number, updatesCurrentAsOf: number, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => (calendarActive: boolean): SessionState => ({ calendarQuery: calendarQuery(calendarActive), messagesCurrentAsOf, updatesCurrentAsOf, watchedIDs: threadWatcher.getWatchedIDs(), }), ); const sessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => (calendarActive: boolean) => SessionState = _memoize( baseSessionStateFuncSelector, ); export { openSocketSelector, queuedReports, getClientResponsesSelector, sessionStateFuncSelector, }; diff --git a/native/selectors/socket-selectors.js b/native/selectors/socket-selectors.js index 152eeed43..ebc9168ed 100644 --- a/native/selectors/socket-selectors.js +++ b/native/selectors/socket-selectors.js @@ -1,110 +1,96 @@ // @flow import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; import { cookieSelector } from 'lib/selectors/keyserver-selectors.js'; import { getClientResponsesSelector, sessionStateFuncSelector, } from 'lib/selectors/socket-selectors.js'; -import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; import type { ClientServerRequest, ClientClientResponse, } from 'lib/types/request-types.js'; import type { SessionIdentification, SessionState, } from 'lib/types/session-types.js'; -import { commCoreModule } from '../native-modules.js'; import { calendarActiveSelector } from '../navigation/nav-selectors.js'; import type { AppState } from '../redux/state-types.js'; import type { NavPlusRedux } from '../types/selector-types.js'; const baseSessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = keyserverID => createSelector( cookieSelector(keyserverID), (cookie: ?string): SessionIdentification => ({ cookie }), ); const sessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = _memoize( baseSessionIdentificationSelector, ); -async function getSignedIdentityKeysBlob(): Promise { - await commCoreModule.initializeCryptoAccount(); - const { blobPayload, signature } = await commCoreModule.getUserPublicKey(); - const signedIdentityKeysBlob: SignedIdentityKeysBlob = { - payload: blobPayload, - signature, - }; - return signedIdentityKeysBlob; -} - type NativeGetClientResponsesSelectorInputType = $ReadOnly<{ ...NavPlusRedux, +getInitialNotificationsEncryptedMessage: () => Promise, +keyserverID: string, }>; const nativeGetClientResponsesSelector: ( input: NativeGetClientResponsesSelectorInputType, ) => ( serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( (input: NativeGetClientResponsesSelectorInputType) => getClientResponsesSelector(input.redux, input.keyserverID), (input: NativeGetClientResponsesSelectorInputType) => calendarActiveSelector(input.navContext), (input: NativeGetClientResponsesSelectorInputType) => input.getInitialNotificationsEncryptedMessage, ( getClientResponsesFunc: ( calendarActive: boolean, - getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: () => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray>, calendarActive: boolean, getInitialNotificationsEncryptedMessage: () => Promise, ) => (serverRequests: $ReadOnlyArray) => getClientResponsesFunc( calendarActive, - getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, serverRequests, ), ); const baseNativeSessionStateFuncSelector: ( keyserverID: string, ) => (input: NavPlusRedux) => () => SessionState = keyserverID => createSelector( (input: NavPlusRedux) => sessionStateFuncSelector(keyserverID)(input.redux), (input: NavPlusRedux) => calendarActiveSelector(input.navContext), ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); const nativeSessionStateFuncSelector: ( keyserverID: string, ) => (input: NavPlusRedux) => () => SessionState = _memoize( baseNativeSessionStateFuncSelector, ); export { sessionIdentificationSelector, nativeGetClientResponsesSelector, nativeSessionStateFuncSelector, }; diff --git a/web/account/account-hooks.js b/web/account/account-hooks.js deleted file mode 100644 index eb5277201..000000000 --- a/web/account/account-hooks.js +++ /dev/null @@ -1,306 +0,0 @@ -// @flow - -import olm from '@commapp/olm'; -import invariant from 'invariant'; -import * as React from 'react'; -import uuid from 'uuid'; - -import type { - SignedIdentityKeysBlob, - CryptoStore, - IdentityKeysBlob, - CryptoStoreContextType, -} from 'lib/types/crypto-types.js'; -import { - type IdentityNewDeviceKeyUpload, - type IdentityExistingDeviceKeyUpload, -} from 'lib/types/identity-service-types.js'; -import { - retrieveIdentityKeysAndPrekeys, - getAccountOneTimeKeys, -} from 'lib/utils/olm-utils.js'; -import { useDispatch } from 'lib/utils/redux-utils.js'; - -import { initOlm } from '../olm/olm-utils.js'; -import { setCryptoStore } from '../redux/crypto-store-reducer.js'; -import { useSelector } from '../redux/redux-utils.js'; - -const CryptoStoreContext: 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 Promise.all([getOrCreateCryptoStore(), 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 useGetNewDeviceKeyUpload(): () => Promise { - const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); - // `getExistingDeviceKeyUpload()` will initialize OLM, so no need to do it - // again - const getExistingDeviceKeyUpload = useGetExistingDeviceKeyUpload(); - const dispatch = useDispatch(); - - return React.useCallback(async () => { - const [ - { - keyPayload, - keyPayloadSignature, - contentPrekey, - contentPrekeySignature, - notifPrekey, - notifPrekeySignature, - }, - cryptoStore, - ] = await Promise.all([ - getExistingDeviceKeyUpload(), - getOrCreateCryptoStore(), - ]); - - const primaryOLMAccount = new olm.Account(); - const notificationOLMAccount = new olm.Account(); - primaryOLMAccount.unpickle( - cryptoStore.primaryAccount.picklingKey, - cryptoStore.primaryAccount.pickledAccount, - ); - notificationOLMAccount.unpickle( - cryptoStore.notificationAccount.picklingKey, - cryptoStore.notificationAccount.pickledAccount, - ); - - const contentOneTimeKeys = getAccountOneTimeKeys(primaryOLMAccount); - const notifOneTimeKeys = getAccountOneTimeKeys(notificationOLMAccount); - - const pickledPrimaryAccount = primaryOLMAccount.pickle( - cryptoStore.primaryAccount.picklingKey, - ); - const pickledNotificationAccount = notificationOLMAccount.pickle( - cryptoStore.notificationAccount.picklingKey, - ); - - const updatedCryptoStore = { - primaryAccount: { - picklingKey: cryptoStore.primaryAccount.picklingKey, - pickledAccount: pickledPrimaryAccount, - }, - primaryIdentityKeys: cryptoStore.primaryIdentityKeys, - notificationAccount: { - picklingKey: cryptoStore.notificationAccount.picklingKey, - pickledAccount: pickledNotificationAccount, - }, - notificationIdentityKeys: cryptoStore.notificationIdentityKeys, - }; - - dispatch({ type: setCryptoStore, payload: updatedCryptoStore }); - - return { - keyPayload, - keyPayloadSignature, - contentPrekey, - contentPrekeySignature, - notifPrekey, - notifPrekeySignature, - contentOneTimeKeys, - notifOneTimeKeys, - }; - }, [dispatch, getOrCreateCryptoStore, getExistingDeviceKeyUpload]); -} - -function useGetExistingDeviceKeyUpload(): () => Promise { - const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); - // `getSignedIdentityKeysBlob()` will initialize OLM, so no need to do it - // again - const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); - const dispatch = useDispatch(); - - return React.useCallback(async () => { - const [ - { payload: keyPayload, signature: keyPayloadSignature }, - cryptoStore, - ] = await Promise.all([ - getSignedIdentityKeysBlob(), - getOrCreateCryptoStore(), - ]); - - const primaryOLMAccount = new olm.Account(); - const notificationOLMAccount = new olm.Account(); - primaryOLMAccount.unpickle( - cryptoStore.primaryAccount.picklingKey, - cryptoStore.primaryAccount.pickledAccount, - ); - notificationOLMAccount.unpickle( - cryptoStore.notificationAccount.picklingKey, - cryptoStore.notificationAccount.pickledAccount, - ); - - const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } = - retrieveIdentityKeysAndPrekeys(primaryOLMAccount); - const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } = - retrieveIdentityKeysAndPrekeys(notificationOLMAccount); - - const pickledPrimaryAccount = primaryOLMAccount.pickle( - cryptoStore.primaryAccount.picklingKey, - ); - const pickledNotificationAccount = notificationOLMAccount.pickle( - cryptoStore.notificationAccount.picklingKey, - ); - - const updatedCryptoStore = { - primaryAccount: { - picklingKey: cryptoStore.primaryAccount.picklingKey, - pickledAccount: pickledPrimaryAccount, - }, - primaryIdentityKeys: cryptoStore.primaryIdentityKeys, - notificationAccount: { - picklingKey: cryptoStore.notificationAccount.picklingKey, - pickledAccount: pickledNotificationAccount, - }, - notificationIdentityKeys: cryptoStore.notificationIdentityKeys, - }; - - dispatch({ type: setCryptoStore, payload: updatedCryptoStore }); - - return { - keyPayload, - keyPayloadSignature, - contentPrekey, - contentPrekeySignature, - notifPrekey, - notifPrekeySignature, - }; - }, [dispatch, getOrCreateCryptoStore, getSignedIdentityKeysBlob]); -} - -export { - useGetSignedIdentityKeysBlob, - useGetOrCreateCryptoStore, - GetOrCreateCryptoStoreProvider, - useGetNewDeviceKeyUpload, - useGetExistingDeviceKeyUpload, -}; diff --git a/web/account/log-in-form.react.js b/web/account/log-in-form.react.js index 0f1b8e24e..564822262 100644 --- a/web/account/log-in-form.react.js +++ b/web/account/log-in-form.react.js @@ -1,87 +1,80 @@ // @flow import { useConnectModal } from '@rainbow-me/rainbowkit'; import * as React from 'react'; import { useWalletClient } from 'wagmi'; import { isDev } from 'lib/utils/dev-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; -import { useGetOrCreateCryptoStore } from './account-hooks.js'; import css from './log-in-form.css'; import SIWEButton from './siwe-button.react.js'; import SIWELoginForm from './siwe-login-form.react.js'; import TraditionalLoginForm from './traditional-login-form.react.js'; import Button from '../components/button.react.js'; import OrBreak from '../components/or-break.react.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; function LoginForm(): React.Node { const { openConnectModal } = useConnectModal(); const { data: signer } = useWalletClient(); const dispatch = useDispatch(); - const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); - - React.useEffect(() => { - void getOrCreateCryptoStore(); - }, [getOrCreateCryptoStore]); - const onQRCodeLoginButtonClick = React.useCallback(() => { dispatch({ type: updateNavInfoActionType, payload: { loginMethod: 'qr-code', }, }); }, [dispatch]); const qrCodeLoginButton = React.useMemo(() => { if (!isDev) { return null; } return (
); }, [onQRCodeLoginButtonClick]); const [siweAuthFlowSelected, setSIWEAuthFlowSelected] = React.useState(false); const onSIWEButtonClick = React.useCallback(() => { setSIWEAuthFlowSelected(true); openConnectModal && openConnectModal(); }, [openConnectModal]); const cancelSIWEAuthFlow = React.useCallback(() => { setSIWEAuthFlowSelected(false); }, []); if (siweAuthFlowSelected && signer) { return (
); } return (
{qrCodeLoginButton}
); } export default LoginForm; diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js index 953b7d5af..91a42005e 100644 --- a/web/account/qr-code-login.react.js +++ b/web/account/qr-code-login.react.js @@ -1,198 +1,167 @@ // @flow -import olm from '@commapp/olm'; import invariant from 'invariant'; import { QRCodeSVG } from 'qrcode.react'; import * as React from 'react'; -import { createSelector } from 'reselect'; import { identityLogInActionTypes } from 'lib/actions/user-actions.js'; import { QRAuthHandler } from 'lib/components/qr-auth-handler.react.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; import { generateKeyCommon } from 'lib/media/aes-crypto-utils-common.js'; import * as AES from 'lib/media/aes-crypto-utils-common.js'; import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; -import type { CryptoStore, PickledOLMAccount } from 'lib/types/crypto-types.js'; import type { NonceChallenge, SignedMessage, } from 'lib/types/identity-service-types.js'; -import type { WebAppState } from 'lib/types/redux-types.js'; import { peerToPeerMessageTypes, type QRCodeAuthMessage, } from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; import { qrCodeAuthMessagePayloadValidator, type QRCodeAuthMessagePayload, } from 'lib/types/tunnelbroker/qr-code-auth-message-types.js'; +import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import css from './qr-code-login.css'; -import { initOlm } from '../olm/olm-utils.js'; -import { useSelector } from '../redux/redux-utils.js'; +import { olmAPI } from '../crypto/olm-api.js'; import { base64DecodeBuffer, base64EncodeBuffer, } from '../utils/base64-utils.js'; import { convertBytesToObj, convertObjToBytes, } from '../utils/conversion-utils.js'; -const deviceIDAndPrimaryAccountSelector: (state: WebAppState) => { - ed25519Key: ?string, - primaryAccount: ?PickledOLMAccount, -} = createSelector( - (state: WebAppState) => state.cryptoStore, - (cryptoStore: ?CryptoStore) => ({ - ed25519Key: cryptoStore?.primaryIdentityKeys.ed25519, - primaryAccount: cryptoStore?.primaryAccount, - }), -); - async function composeTunnelbrokerMessage( encryptionKey: string, obj: QRCodeAuthMessagePayload, ): Promise { const objBytes = convertObjToBytes(obj); const keyBytes = hexToUintArray(encryptionKey); const encryptedBytes = await AES.encryptCommon(crypto, keyBytes, objBytes); const encryptedContent = base64EncodeBuffer(encryptedBytes); return { type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE, encryptedContent, }; } async function parseTunnelbrokerMessage( encryptionKey: string, message: QRCodeAuthMessage, ): Promise { const encryptedData = base64DecodeBuffer(message.encryptedContent); const decryptedData = await AES.decryptCommon( crypto, hexToUintArray(encryptionKey), new Uint8Array(encryptedData), ); const payload = convertBytesToObj(decryptedData); if (!qrCodeAuthMessagePayloadValidator.is(payload)) { return null; } return payload; } function QrCodeLogin(): React.Node { const [qrCodeValue, setQrCodeValue] = React.useState(); - const { ed25519Key, primaryAccount } = useSelector( - deviceIDAndPrimaryAccountSelector, - ); + const [deviceKeys, setDeviceKeys] = React.useState(); const { setUnauthorizedDeviceID } = useTunnelbroker(); const identityContext = React.useContext(IdentityClientContext); const identityClient = identityContext?.identityClient; const dispatchActionPromise = useDispatchActionPromise(); const performRegistration = React.useCallback( async (userID: string) => { - if (!primaryAccount) { - return; - } invariant(identityClient, 'identity context not set'); try { - await initOlm(); - const primaryOLMAccount = new olm.Account(); - primaryOLMAccount.unpickle( - primaryAccount.picklingKey, - primaryAccount.pickledAccount, - ); - const nonce = await identityClient.generateNonce(); const nonceChallenge: NonceChallenge = { nonce }; const nonceMessage = JSON.stringify(nonceChallenge); - const signature = await primaryOLMAccount.sign(nonceMessage); + const signature = await olmAPI.signMessage(nonceMessage); const challengeResponse: SignedMessage = { message: nonceMessage, signature, }; await dispatchActionPromise( identityLogInActionTypes, identityClient.uploadKeysForRegisteredDeviceAndLogIn( userID, challengeResponse, ), ); setUnauthorizedDeviceID(null); } catch (err) { console.error('Secondary device registration error:', err); } }, - [ - dispatchActionPromise, - identityClient, - primaryAccount, - setUnauthorizedDeviceID, - ], + [dispatchActionPromise, identityClient, setUnauthorizedDeviceID], ); const generateQRCode = React.useCallback(async () => { try { - if (!ed25519Key) { + const ed25519 = await getContentSigningKey(); + if (!ed25519) { return; } const rawAESKey: Uint8Array = await generateKeyCommon(crypto); const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey); - const url = qrCodeLinkURL(aesKeyAsHexString, ed25519Key); - setUnauthorizedDeviceID(ed25519Key); + const url = qrCodeLinkURL(aesKeyAsHexString, ed25519); + setUnauthorizedDeviceID(ed25519); setQrCodeValue(url); - setDeviceKeys({ deviceID: ed25519Key, aesKey: aesKeyAsHexString }); + setDeviceKeys({ deviceID: ed25519, aesKey: aesKeyAsHexString }); } catch (err) { console.error('Failed to generate QR Code:', err); } - }, [ed25519Key, setUnauthorizedDeviceID]); + }, [setUnauthorizedDeviceID]); React.useEffect(() => { void generateQRCode(); }, [generateQRCode]); return ( <>
Log in to Comm
Open the Comm app on your phone and scan the QR code below
How to find the scanner:
Go to Profile
Select Linked devices
Click Add on the top right
); } export default QrCodeLogin; diff --git a/web/account/siwe-login-form.react.js b/web/account/siwe-login-form.react.js index b4cb7a4a9..cbef7c602 100644 --- a/web/account/siwe-login-form.react.js +++ b/web/account/siwe-login-form.react.js @@ -1,328 +1,314 @@ // @flow import '@rainbow-me/rainbowkit/styles.css'; import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { useAccount, useWalletClient } from 'wagmi'; import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; import { getSIWENonce, getSIWENonceActionTypes, siweAuth, siweAuthActionTypes, } from 'lib/actions/siwe-actions.js'; import { identityGenerateNonceActionTypes, useIdentityGenerateNonce, identityLogInActionTypes, useIdentityWalletLogIn, } from 'lib/actions/user-actions.js'; import ConnectedWalletInfo from 'lib/components/connected-wallet-info.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import stores from 'lib/facts/stores.js'; import { logInExtraInfoSelector } from 'lib/selectors/account-selectors.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LogInStartingPayload, LogInExtraInfo, } from 'lib/types/account-types.js'; -import type { OLMIdentityKeys } from 'lib/types/crypto-types.js'; import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; import { getMessageForException, ServerError } from 'lib/utils/errors.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; import { createSIWEMessage, getSIWEStatementForPublicKey, siweMessageSigningExplanationStatements, } from 'lib/utils/siwe-utils.js'; -import { useGetSignedIdentityKeysBlob } from './account-hooks.js'; import HeaderSeparator from './header-separator.react.js'; import css from './siwe.css'; import Button from '../components/button.react.js'; import OrBreak from '../components/or-break.react.js'; +import { olmAPI } from '../crypto/olm-api.js'; import LoadingIndicator from '../loading-indicator.react.js'; import { useSelector } from '../redux/redux-utils.js'; type SIWELogInError = 'account_does_not_exist'; type SIWELoginFormProps = { +cancelSIWEAuthFlow: () => void, }; const legacyGetSIWENonceLoadingStatusSelector = createLoadingStatusSelector( getSIWENonceActionTypes, ); const identityGenerateNonceLoadingStatusSelector = createLoadingStatusSelector( identityGenerateNonceActionTypes, ); const legacySiweAuthLoadingStatusSelector = createLoadingStatusSelector(siweAuthActionTypes); function SIWELoginForm(props: SIWELoginFormProps): React.Node { const { address } = useAccount(); const { data: signer } = useWalletClient(); const dispatchActionPromise = useDispatchActionPromise(); const legacyGetSIWENonceCall = useLegacyAshoatKeyserverCall(getSIWENonce); const legacyGetSIWENonceCallLoadingStatus = useSelector( legacyGetSIWENonceLoadingStatusSelector, ); const identityGenerateNonce = useIdentityGenerateNonce(); const identityGenerateNonceLoadingStatus = useSelector( identityGenerateNonceLoadingStatusSelector, ); const siweAuthLoadingStatus = useSelector( legacySiweAuthLoadingStatusSelector, ); const legacySiweAuthCall = useLegacyAshoatKeyserverCall(siweAuth); const logInExtraInfo = useSelector(logInExtraInfoSelector); const identityWalletLogIn = useIdentityWalletLogIn(); const [siweNonce, setSIWENonce] = React.useState(null); const siweNonceShouldBeFetched = !siweNonce && legacyGetSIWENonceCallLoadingStatus !== 'loading' && identityGenerateNonceLoadingStatus !== 'loading'; React.useEffect(() => { if (!siweNonceShouldBeFetched) { return; } if (usingCommServicesAccessToken) { void dispatchActionPromise( identityGenerateNonceActionTypes, (async () => { const response = await identityGenerateNonce(); setSIWENonce(response); })(), ); } else { void dispatchActionPromise( getSIWENonceActionTypes, (async () => { const response = await legacyGetSIWENonceCall(); setSIWENonce(response); })(), ); } }, [ dispatchActionPromise, identityGenerateNonce, legacyGetSIWENonceCall, siweNonceShouldBeFetched, ]); - const primaryIdentityPublicKeys: ?OLMIdentityKeys = useSelector( - state => state.cryptoStore?.primaryIdentityKeys, - ); - - const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); - const callLegacySIWEAuthEndpoint = React.useCallback( async (message: string, signature: string, extraInfo: LogInExtraInfo) => { - const signedIdentityKeysBlob = await getSignedIdentityKeysBlob(); - invariant( - signedIdentityKeysBlob, - 'signedIdentityKeysBlob must be set in attemptSIWEAuth', - ); + await olmAPI.initializeCryptoAccount(); + const userPublicKey = await olmAPI.getUserPublicKey(); try { return await legacySiweAuthCall({ message, signature, - signedIdentityKeysBlob, + signedIdentityKeysBlob: { + payload: userPublicKey.blobPayload, + signature: userPublicKey.signature, + }, doNotRegister: true, ...extraInfo, }); } catch (e) { if ( e instanceof ServerError && e.message === 'account_does_not_exist' ) { setError('account_does_not_exist'); } throw e; } }, - [getSignedIdentityKeysBlob, legacySiweAuthCall], + [legacySiweAuthCall], ); const attemptLegacySIWEAuth = React.useCallback( (message: string, signature: string) => { return dispatchActionPromise( siweAuthActionTypes, callLegacySIWEAuthEndpoint(message, signature, logInExtraInfo), undefined, ({ calendarQuery: logInExtraInfo.calendarQuery }: LogInStartingPayload), ); }, [callLegacySIWEAuthEndpoint, dispatchActionPromise, logInExtraInfo], ); const attemptIdentityWalletLogIn = React.useCallback( (walletAddress: string, siweMessage: string, siweSignature: string) => { return dispatchActionPromise( identityLogInActionTypes, (async () => { try { return await identityWalletLogIn( walletAddress, siweMessage, siweSignature, ); } catch (e) { if (getMessageForException(e) === 'user not found') { setError('account_does_not_exist'); } throw e; } })(), ); }, [dispatchActionPromise, identityWalletLogIn], ); const dispatch = useDispatch(); const onSignInButtonClick = React.useCallback(async () => { invariant(signer, 'signer must be present during SIWE attempt'); invariant(siweNonce, 'nonce must be present during SIWE attempt'); - invariant( - primaryIdentityPublicKeys, - 'primaryIdentityPublicKeys must be present during SIWE attempt', - ); - const statement = getSIWEStatementForPublicKey( - primaryIdentityPublicKeys.ed25519, - ); + await olmAPI.initializeCryptoAccount(); + const { + primaryIdentityPublicKeys: { ed25519 }, + } = await olmAPI.getUserPublicKey(); + const statement = getSIWEStatementForPublicKey(ed25519); const message = createSIWEMessage(address, statement, siweNonce); const signature = await signer.signMessage({ message }); if (usingCommServicesAccessToken) { await attemptIdentityWalletLogIn(address, message, signature); } else { await attemptLegacySIWEAuth(message, signature); dispatch({ type: setDataLoadedActionType, payload: { dataLoaded: true, }, }); } }, [ address, attemptLegacySIWEAuth, attemptIdentityWalletLogIn, - primaryIdentityPublicKeys, signer, siweNonce, dispatch, ]); const { cancelSIWEAuthFlow } = props; const backButtonColor = React.useMemo( () => ({ backgroundColor: '#211E2D' }), [], ); const signInButtonColor = React.useMemo( () => ({ backgroundColor: '#6A20E3' }), [], ); const [error, setError] = React.useState(); const mainMiddleAreaClassName = classNames({ [css.mainMiddleArea]: true, [css.hidden]: !!error, }); const errorOverlayClassNames = classNames({ [css.errorOverlay]: true, [css.hidden]: !error, }); - if ( - siweAuthLoadingStatus === 'loading' || - !siweNonce || - !primaryIdentityPublicKeys - ) { + if (siweAuthLoadingStatus === 'loading' || !siweNonce) { return (
); } let errorText; if (error === 'account_does_not_exist') { errorText = ( <>

No Comm account found for that Ethereum wallet!

We require that users register on their mobile devices. Comm relies on a primary device capable of scanning QR codes in order to authorize secondary devices.

You can install our iOS app  here , or our Android app  here .

); } return (

Sign in with Ethereum

Wallet Connected

{siweMessageSigningExplanationStatements}

By signing up, you agree to our{' '} Terms of Use &{' '} Privacy Policy.

{errorText}
); } export default SIWELoginForm; diff --git a/web/account/traditional-login-form.react.js b/web/account/traditional-login-form.react.js index f2ecd410a..ed59b4bfc 100644 --- a/web/account/traditional-login-form.react.js +++ b/web/account/traditional-login-form.react.js @@ -1,242 +1,233 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { useLogIn, logInActionTypes, useIdentityPasswordLogIn, identityLogInActionTypes, } from 'lib/actions/user-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { logInExtraInfoSelector } from 'lib/selectors/account-selectors.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { oldValidUsernameRegex, validEmailRegex, } from 'lib/shared/account-utils.js'; import type { LogInExtraInfo, LogInStartingPayload, } from 'lib/types/account-types.js'; import { logInActionSources } from 'lib/types/account-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; -import { useGetSignedIdentityKeysBlob } from './account-hooks.js'; import HeaderSeparator from './header-separator.react.js'; import css from './log-in-form.css'; import PasswordInput from './password-input.react.js'; import Button from '../components/button.react.js'; +import { olmAPI } from '../crypto/olm-api.js'; import LoadingIndicator from '../loading-indicator.react.js'; import Input from '../modals/input.react.js'; import { useSelector } from '../redux/redux-utils.js'; const loadingStatusSelector = createLoadingStatusSelector(logInActionTypes); function TraditionalLoginForm(): React.Node { const inputDisabled = useSelector(loadingStatusSelector) === 'loading'; const loginExtraInfo = useSelector(logInExtraInfoSelector); const callLegacyLogIn = useLogIn(); const callIdentityPasswordLogIn = useIdentityPasswordLogIn(); const dispatchActionPromise = useDispatchActionPromise(); const modalContext = useModalContext(); - const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); - const usernameInputRef = React.useRef(); React.useEffect(() => { usernameInputRef.current?.focus(); }, []); const [username, setUsername] = React.useState(''); const onUsernameChange = React.useCallback( (e: SyntheticEvent) => { invariant(e.target instanceof HTMLInputElement, 'target not input'); setUsername(e.target.value); }, [], ); const onUsernameBlur = React.useCallback(() => { setUsername(untrimmedUsername => untrimmedUsername.trim()); }, []); const [password, setPassword] = React.useState(''); const onPasswordChange = React.useCallback( (e: SyntheticEvent) => { invariant(e.target instanceof HTMLInputElement, 'target not input'); setPassword(e.target.value); }, [], ); const [errorMessage, setErrorMessage] = React.useState(''); const legacyLogInAction = React.useCallback( async (extraInfo: LogInExtraInfo) => { - const signedIdentityKeysBlob = await getSignedIdentityKeysBlob(); + await olmAPI.initializeCryptoAccount(); + const userPublicKey = await olmAPI.getUserPublicKey(); try { - invariant( - signedIdentityKeysBlob, - 'signedIdentityKeysBlob must be set in logInAction', - ); - const result = await callLegacyLogIn({ ...extraInfo, username, password, authActionSource: logInActionSources.logInFromWebForm, - signedIdentityKeysBlob, + signedIdentityKeysBlob: { + payload: userPublicKey.blobPayload, + signature: userPublicKey.signature, + }, }); modalContext.popModal(); return result; } catch (e) { setUsername(''); setPassword(''); if (getMessageForException(e) === 'invalid_credentials') { setErrorMessage('incorrect username or password'); } else { setErrorMessage('unknown error'); } usernameInputRef.current?.focus(); throw e; } }, - [ - callLegacyLogIn, - modalContext, - password, - getSignedIdentityKeysBlob, - username, - ], + [callLegacyLogIn, modalContext, password, username], ); const identityPasswordLogInAction = React.useCallback(async () => { try { const result = await callIdentityPasswordLogIn(username, password); modalContext.popModal(); return result; } catch (e) { setUsername(''); setPassword(''); const messageForException = getMessageForException(e); if ( messageForException === 'user not found' || messageForException === 'login failed' ) { setErrorMessage('incorrect username or password'); } else { setErrorMessage('unknown error'); } usernameInputRef.current?.focus(); throw e; } }, [callIdentityPasswordLogIn, modalContext, password, username]); const onSubmit = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); if (username.search(validEmailRegex) > -1) { setUsername(''); setErrorMessage('usernames only, not emails'); usernameInputRef.current?.focus(); return; } else if (username.search(oldValidUsernameRegex) === -1) { setUsername(''); setErrorMessage('alphanumeric usernames only'); usernameInputRef.current?.focus(); return; } else if (password === '') { setErrorMessage('password is empty'); usernameInputRef.current?.focus(); return; } if (usingCommServicesAccessToken) { void dispatchActionPromise( identityLogInActionTypes, identityPasswordLogInAction(), ); } else { void dispatchActionPromise( logInActionTypes, legacyLogInAction(loginExtraInfo), undefined, ({ calendarQuery: loginExtraInfo.calendarQuery, }: LogInStartingPayload), ); } }, [ dispatchActionPromise, identityPasswordLogInAction, legacyLogInAction, loginExtraInfo, username, password, ], ); const loginButtonContent = React.useMemo(() => { if (inputDisabled) { return ; } return 'Sign in'; }, [inputDisabled]); const signInButtonColor = React.useMemo( () => ({ backgroundColor: '#6A20E3' }), [], ); return (

Sign in to Comm

Username
Password
{errorMessage}
); } export default TraditionalLoginForm; diff --git a/web/crypto/olm-api.js b/web/crypto/olm-api.js index 961e65265..45185e594 100644 --- a/web/crypto/olm-api.js +++ b/web/crypto/olm-api.js @@ -1,65 +1,57 @@ // @flow -import olm from '@commapp/olm'; - import { type OlmAPI } from 'lib/types/crypto-types.js'; import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js'; import { getOlmWasmPath } from '../shared-worker/utils/constants.js'; import { workerRequestMessageTypes, workerResponseMessageTypes, } from '../types/worker-types.js'; -const usingSharedWorker = false; - function proxyToWorker( method: $Keys, ): (...args: $ReadOnlyArray) => Promise { return async (...args: $ReadOnlyArray) => { const sharedWorker = await getCommSharedWorker(); const result = await sharedWorker.schedule({ type: workerRequestMessageTypes.CALL_OLM_API_METHOD, method, args, }); if (!result) { throw new Error(`Worker OlmAPI call didn't return expected message`); } else if (result.type !== workerResponseMessageTypes.CALL_OLM_API_METHOD) { throw new Error( `Worker OlmAPI call didn't return expected message. Instead got: ${JSON.stringify( result, )}`, ); } // Worker should return a message with the corresponding return type return (result.result: any); }; } const olmAPI: OlmAPI = { async initializeCryptoAccount(): Promise { - if (usingSharedWorker) { - const sharedWorker = await getCommSharedWorker(); - await sharedWorker.schedule({ - type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, - olmWasmPath: getOlmWasmPath(), - }); - } else { - await olm.init(); - } + const sharedWorker = await getCommSharedWorker(); + await sharedWorker.schedule({ + type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, + olmWasmPath: getOlmWasmPath(), + }); }, getUserPublicKey: proxyToWorker('getUserPublicKey'), encrypt: proxyToWorker('encrypt'), decrypt: proxyToWorker('decrypt'), contentInboundSessionCreator: proxyToWorker('contentInboundSessionCreator'), contentOutboundSessionCreator: proxyToWorker('contentOutboundSessionCreator'), notificationsSessionCreator: proxyToWorker('notificationsSessionCreator'), getOneTimeKeys: proxyToWorker('getOneTimeKeys'), validateAndUploadPrekeys: proxyToWorker('validateAndUploadPrekeys'), signMessage: proxyToWorker('signMessage'), }; -export { olmAPI, usingSharedWorker }; +export { olmAPI }; diff --git a/web/selectors/socket-selectors.js b/web/selectors/socket-selectors.js index 0dafa5bcc..8d3bb8d19 100644 --- a/web/selectors/socket-selectors.js +++ b/web/selectors/socket-selectors.js @@ -1,108 +1,101 @@ // @flow import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; import { sessionIDSelector, cookieSelector, } from 'lib/selectors/keyserver-selectors.js'; import { getClientResponsesSelector, sessionStateFuncSelector, } from 'lib/selectors/socket-selectors.js'; -import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; import type { ClientServerRequest, ClientClientResponse, } from 'lib/types/request-types.js'; import type { SessionIdentification, SessionState, } from 'lib/types/session-types.js'; import type { AppState } from '../redux/redux-setup.js'; const baseSessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = keyserverID => createSelector( cookieSelector(keyserverID), sessionIDSelector(keyserverID), (cookie: ?string, sessionID: ?string): SessionIdentification => ({ cookie, sessionID, }), ); const sessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = _memoize( baseSessionIdentificationSelector, ); type WebGetClientResponsesSelectorInputType = { +state: AppState, - +getSignedIdentityKeysBlob: () => Promise, +getInitialNotificationsEncryptedMessage: () => Promise, +keyserverID: string, }; const webGetClientResponsesSelector: ( input: WebGetClientResponsesSelectorInputType, ) => ( serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( (input: WebGetClientResponsesSelectorInputType) => getClientResponsesSelector(input.state, input.keyserverID), - (input: WebGetClientResponsesSelectorInputType) => - input.getSignedIdentityKeysBlob, (input: WebGetClientResponsesSelectorInputType) => input.state.navInfo.tab === 'calendar', (input: WebGetClientResponsesSelectorInputType) => input.getInitialNotificationsEncryptedMessage, ( getClientResponsesFunc: ( calendarActive: boolean, - getSignedIdentityKeysBlob: () => Promise, getInitialNotificationsEncryptedMessage: () => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray>, - getSignedIdentityKeysBlob: () => Promise, calendarActive: boolean, getInitialNotificationsEncryptedMessage: () => Promise, ) => (serverRequests: $ReadOnlyArray) => getClientResponsesFunc( calendarActive, - getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, serverRequests, ), ); const baseWebSessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => () => SessionState = keyserverID => createSelector( sessionStateFuncSelector(keyserverID), (state: AppState) => state.navInfo.tab === 'calendar', ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); const webSessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => () => SessionState = _memoize( baseWebSessionStateFuncSelector, ); export { sessionIdentificationSelector, webGetClientResponsesSelector, webSessionStateFuncSelector, }; diff --git a/web/socket.react.js b/web/socket.react.js index ed3683de4..a26ea0334 100644 --- a/web/socket.react.js +++ b/web/socket.react.js @@ -1,114 +1,111 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { preRequestUserStateForSingleKeyserverSelector } from 'lib/selectors/account-selectors.js'; import { cookieSelector, 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 { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; -import { useGetSignedIdentityKeysBlob } 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 { keyserverID } = props; const cookie = useSelector(cookieSelector(keyserverID)); const connection = useSelector(connectionSelector(keyserverID)); invariant(connection, 'keyserver missing from keyserverStore'); const active = useSelector( state => !!state.currentUserInfo && !state.currentUserInfo.anonymous && state.lifecycleState !== 'background', ); const openSocket = useSelector(openSocketSelector(keyserverID)); invariant(openSocket, 'openSocket failed to be created'); const sessionIdentification = useSelector( sessionIdentificationSelector(keyserverID), ); const preRequestUserState = useSelector( preRequestUserStateForSingleKeyserverSelector(keyserverID), ); - const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); const getInitialNotificationsEncryptedMessage = useInitialNotificationsEncryptedMessage(keyserverID); const getClientResponses = useSelector(state => webGetClientResponsesSelector({ state, - getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, keyserverID, }), ); const sessionStateFunc = useSelector( webSessionStateFuncSelector(keyserverID), ); 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(keyserverID), ); const activeSessionRecovery = useSelector( state => state.keyserverStore.keyserverInfos[keyserverID]?.connection .activeSessionRecovery, ); return ( ); }); export default WebSocket;