diff --git a/lib/hooks/peer-list-hooks.js b/lib/hooks/peer-list-hooks.js new file mode 100644 index 000000000..2e59cd130 --- /dev/null +++ b/lib/hooks/peer-list-hooks.js @@ -0,0 +1,40 @@ +// @flow + +import invariant from 'invariant'; +import * as React from 'react'; + +import { setPeerDeviceListsActionType } from '../actions/aux-user-actions.js'; +import { getRelativeUserIDs } from '../selectors/user-selectors.js'; +import { IdentityClientContext } from '../shared/identity-client-context.js'; +import { convertSignedDeviceListsToRawDeviceLists } from '../utils/device-list-utils.js'; +import { useDispatch, useSelector } from '../utils/redux-utils.js'; + +function useCreateInitialPeerList(): () => Promise { + const dispatch = useDispatch(); + const relativeUserIDs = useSelector(getRelativeUserIDs); + + const identityContext = React.useContext(IdentityClientContext); + invariant(identityContext, 'Identity context should be set'); + + return React.useCallback(async () => { + if (!identityContext) { + return; + } + try { + const userDeviceLists = + await identityContext.identityClient.getDeviceListsForUsers( + relativeUserIDs, + ); + const usersRawDeviceLists = + convertSignedDeviceListsToRawDeviceLists(userDeviceLists); + dispatch({ + type: setPeerDeviceListsActionType, + payload: { deviceLists: usersRawDeviceLists }, + }); + } catch (e) { + console.log(`Error creating initial peer list: ${e.message}`); + } + }, [dispatch, identityContext, relativeUserIDs]); +} + +export { useCreateInitialPeerList }; diff --git a/native/profile/tunnelbroker-menu.react.js b/native/profile/tunnelbroker-menu.react.js index 15522363a..05a79e90d 100644 --- a/native/profile/tunnelbroker-menu.react.js +++ b/native/profile/tunnelbroker-menu.react.js @@ -1,264 +1,276 @@ // @flow import * as React from 'react'; import { useState } from 'react'; import { Text, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; +import { useCreateInitialPeerList } from 'lib/hooks/peer-list-hooks.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; import { tunnelbrokerMessageTypes, type TunnelbrokerMessage, } from 'lib/types/tunnelbroker/messages.js'; import { type EncryptedMessage, peerToPeerMessageTypes, } from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; import { createOlmSessionsWithOwnDevices, getContentSigningKey, } from 'lib/utils/crypto-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import Button from '../components/button.react.js'; import TextInput from '../components/text-input.react.js'; import { olmAPI } from '../crypto/olm-api.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useColors, useStyles } from '../themes/colors.js'; type Props = { +navigation: ProfileNavigationProp<'TunnelbrokerMenu'>, +route: NavigationRoute<'TunnelbrokerMenu'>, }; // eslint-disable-next-line no-unused-vars function TunnelbrokerMenu(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const currentUserID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const identityContext = React.useContext(IdentityClientContext); const { connected, addListener, sendMessage, removeListener } = useTunnelbroker(); const [messages, setMessages] = useState([]); const [recipient, setRecipient] = useState(''); const [message, setMessage] = useState(''); const [deviceID, setDeviceID] = React.useState(); React.useEffect(() => { void (async () => { const contentSigningKey = await getContentSigningKey(); setDeviceID(contentSigningKey); })(); }, []); const listener = React.useCallback((msg: TunnelbrokerMessage) => { setMessages(prev => [...prev, msg]); }, []); React.useEffect(() => { addListener(listener); return () => removeListener(listener); }, [addListener, listener, removeListener]); const onSubmit = React.useCallback(async () => { try { await sendMessage({ deviceID: recipient, payload: message }); } catch (e) { console.log(e.message); } }, [message, recipient, sendMessage]); const onCreateSessions = React.useCallback(async () => { if (!identityContext) { return; } const authMetadata = await identityContext.getAuthMetadata(); try { await createOlmSessionsWithOwnDevices( authMetadata, identityContext.identityClient, sendMessage, ); } catch (e) { console.log(`Error creating olm sessions with own devices: ${e.message}`); } }, [identityContext, sendMessage]); + const onCreateInitialPeerList = useCreateInitialPeerList(); + const onSendEncryptedMessage = React.useCallback(async () => { try { if (!currentUserID) { return; } await olmAPI.initializeCryptoAccount(); const encryptedData = await olmAPI.encrypt( `Encrypted message to ${recipient}`, recipient, ); const signingKey = await getContentSigningKey(); const encryptedMessage: EncryptedMessage = { type: peerToPeerMessageTypes.ENCRYPTED_MESSAGE, senderInfo: { deviceID: signingKey, userID: currentUserID, }, encryptedData, }; await sendMessage({ deviceID: recipient, payload: JSON.stringify(encryptedMessage), }); } catch (e) { console.log(`Error sending encrypted content to device: ${e.message}`); } }, [currentUserID, recipient, sendMessage]); return ( INFO Connected {connected.toString()} DEVICE ID {deviceID} USER ID {currentUserID} SEND MESSAGE Recipient Message + MESSAGES {messages .filter(msg => msg.type !== tunnelbrokerMessageTypes.HEARTBEAT) .map((msg, id) => ( {JSON.stringify(msg)} ))} ); } const unboundStyles = { scrollViewContentContainer: { paddingTop: 24, }, scrollView: { backgroundColor: 'panelBackground', }, section: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, marginBottom: 24, marginVertical: 2, }, header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 3, paddingHorizontal: 24, }, submenuButton: { flexDirection: 'row', paddingHorizontal: 24, paddingVertical: 10, alignItems: 'center', }, submenuText: { color: 'panelForegroundLabel', flex: 1, fontSize: 16, }, text: { color: 'panelForegroundLabel', fontSize: 16, }, row: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 14, }, textInput: { color: 'modalBackgroundLabel', flex: 1, fontSize: 16, margin: 0, padding: 0, borderBottomColor: 'transparent', }, }; export default TunnelbrokerMenu; diff --git a/web/settings/account-settings.react.js b/web/settings/account-settings.react.js index adb566188..a6efe9ce4 100644 --- a/web/settings/account-settings.react.js +++ b/web/settings/account-settings.react.js @@ -1,309 +1,284 @@ // @flow import * as React from 'react'; -import { setPeerDeviceListsActionType } from 'lib/actions/aux-user-actions.js'; import { useLogOut, logOutActionTypes } from 'lib/actions/user-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import { useStringForUser } from 'lib/hooks/ens-cache.js'; -import { getRelativeUserIDs } from 'lib/selectors/user-selectors.js'; +import { useCreateInitialPeerList } from 'lib/hooks/peer-list-hooks.js'; import { accountHasPassword } from 'lib/shared/account-utils.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; import { createOlmSessionsWithOwnDevices, getContentSigningKey, } from 'lib/utils/crypto-utils.js'; -import { convertSignedDeviceListsToRawDeviceLists } from 'lib/utils/device-list-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; -import { useDispatch } from 'lib/utils/redux-utils.js'; import css from './account-settings.css'; import AppearanceChangeModal from './appearance-change-modal.react.js'; import BackupTestRestoreModal from './backup-test-restore-modal.react.js'; import PasswordChangeModal from './password-change-modal.js'; import BlockListModal from './relationship/block-list-modal.react.js'; import FriendListModal from './relationship/friend-list-modal.react.js'; import TunnelbrokerMessagesScreen from './tunnelbroker-message-list.react.js'; import TunnelbrokerTestScreen from './tunnelbroker-test.react.js'; import EditUserAvatar from '../avatars/edit-user-avatar.react.js'; import Button from '../components/button.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; function AccountSettings(): React.Node { const sendLogoutRequest = useLogOut(); const dispatchActionPromise = useDispatchActionPromise(); const logOutUser = React.useCallback( () => dispatchActionPromise(logOutActionTypes, sendLogoutRequest()), [dispatchActionPromise, sendLogoutRequest], ); const identityContext = React.useContext(IdentityClientContext); const userID = useSelector(state => state.currentUserInfo?.id); const [deviceID, setDeviceID] = React.useState(); React.useEffect(() => { void (async () => { const contentSigningKey = await getContentSigningKey(); setDeviceID(contentSigningKey); })(); }, []); const { pushModal, popModal } = useModalContext(); const showPasswordChangeModal = React.useCallback( () => pushModal(), [pushModal], ); const openFriendList = React.useCallback( () => pushModal(), [pushModal], ); const openBlockList = React.useCallback( () => pushModal(), [pushModal], ); const isAccountWithPassword = useSelector(state => accountHasPassword(state.currentUserInfo), ); const currentUserInfo = useSelector(state => state.currentUserInfo); const stringForUser = useStringForUser(currentUserInfo); const staffCanSee = useStaffCanSee(); const { sendMessage, connected, addListener, removeListener } = useTunnelbroker(); const openTunnelbrokerModal = React.useCallback( () => pushModal( , ), [popModal, pushModal, sendMessage], ); const openTunnelbrokerMessagesModal = React.useCallback( () => pushModal( , ), [addListener, popModal, pushModal, removeListener], ); const onCreateOlmSessions = React.useCallback(async () => { if (!identityContext) { return; } const authMetadata = await identityContext.getAuthMetadata(); try { await createOlmSessionsWithOwnDevices( authMetadata, identityContext.identityClient, sendMessage, ); } catch (e) { console.log(`Error creating olm sessions with own devices: ${e.message}`); } }, [identityContext, sendMessage]); - const dispatch = useDispatch(); - const relativeUserIDs = useSelector(getRelativeUserIDs); - - const onCreateInitialPeerList = React.useCallback(async () => { - if (!identityContext) { - return; - } - - try { - const userDeviceLists = - await identityContext.identityClient.getDeviceListsForUsers( - relativeUserIDs, - ); - const usersRawDeviceLists = - convertSignedDeviceListsToRawDeviceLists(userDeviceLists); - dispatch({ - type: setPeerDeviceListsActionType, - payload: { deviceLists: usersRawDeviceLists }, - }); - } catch (e) { - console.log(`Error creating initial peer list: ${e.message}`); - } - }, [dispatch, identityContext, relativeUserIDs]); + const onCreateInitialPeerList = useCreateInitialPeerList(); const openBackupTestRestoreModal = React.useCallback( () => pushModal(), [popModal, pushModal], ); const showAppearanceModal = React.useCallback( () => pushModal(), [pushModal], ); if (!currentUserInfo || currentUserInfo.anonymous) { return null; } let changePasswordSection; if (isAccountWithPassword) { changePasswordSection = (
  • Password ******
  • ); } let preferences; if (staffCanSee) { preferences = (

    Preferences

    • Appearance
    ); } let tunnelbroker; if (staffCanSee) { tunnelbroker = (

    Tunnelbroker menu

    • Connected {connected.toString()}
    • Send message to device
    • Trace received messages
    • Create session with own devices
    • Create initial peer list
    ); } let backup; if (staffCanSee) { backup = (

    Backup menu

    • Test backup restore
    ); } let deviceData; if (staffCanSee) { deviceData = (

    Device ID

    • {deviceID}

    User ID

    • {userID}
    ); } return (

    My Account

    • {'Logged in as '} {stringForUser}

    • {changePasswordSection}
    • Friend List
    • Block List
    {preferences} {tunnelbroker} {backup} {deviceData}
    ); } export default AccountSettings;