From 121f8927ed0c3d540ee19dcdcd9fad65958635a8 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 18 Oct 2021 16:43:40 +1100 Subject: [PATCH 1/5] exclude same /24 subnet from onion path building candidates --- ts/session/onions/onionPath.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ts/session/onions/onionPath.ts b/ts/session/onions/onionPath.ts index 9b27809e78..b70fad49d7 100644 --- a/ts/session/onions/onionPath.ts +++ b/ts/session/onions/onionPath.ts @@ -464,7 +464,23 @@ async function buildNewOnionPathsWorker() { if (allNodes.length <= SnodePool.minSnodePoolCount) { throw new Error('Too few nodes to build an onion path. Even after fetching from seed.'); } - const otherNodes = _.shuffle(_.differenceBy(allNodes, guardNodes, 'pubkey_ed25519')); + + // make sure to not reuse multiple times the same subnet /24 + const allNodesGroupedBySubnet24 = _.groupBy(allNodes, e => { + const lastDot = e.ip.lastIndexOf('.'); + return e.ip.substr(0, lastDot); + }); + const oneNodeForEachSubnet24 = _.map(allNodesGroupedBySubnet24, group => { + return _.sample(group) as Data.Snode; + }); + if (oneNodeForEachSubnet24.length <= SnodePool.minSnodePoolCount) { + throw new Error( + 'Too few nodes "unique by ip" to build an onion path. Even after fetching from seed.' + ); + } + const otherNodes = _.shuffle( + _.differenceBy(oneNodeForEachSubnet24, guardNodes, 'pubkey_ed25519') + ); const guards = _.shuffle(guardNodes); // Create path for every guard node: From 4d72f24fd5409a6eaf7dabfc1e2cfec7228a376a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 18 Oct 2021 17:14:14 +1100 Subject: [PATCH 2/5] make sure test ip for snodes are random --- .../session/unit/onion/OnionErrors_test.ts | 23 +++---------------- ts/test/test-utils/utils/pubkey.ts | 5 ++-- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/ts/test/session/unit/onion/OnionErrors_test.ts b/ts/test/session/unit/onion/OnionErrors_test.ts index 30db105ed7..afc8b3be5f 100644 --- a/ts/test/session/unit/onion/OnionErrors_test.ts +++ b/ts/test/session/unit/onion/OnionErrors_test.ts @@ -18,6 +18,7 @@ import AbortController from 'abort-controller'; import * as Data from '../../../../../ts/data/data'; import { pathFailureCount } from '../../../../session/onions/onionPath'; import { SeedNodeAPI } from '../../../../session/seed_node_api'; +import { generateFakeSnodeWithEdKey } from '../../../test-utils/utils'; chai.use(chaiAsPromised as any); chai.should(); @@ -63,8 +64,6 @@ describe('OnionPathsErrors', () => { fakeSwarmForAssociatedWith: Array; let oldOnionPaths: Array>; - const fakeIP = '8.8.8.8'; - let fakePortCurrent = 20000; beforeEach(async () => { guardPubkeys = TestUtils.generateFakePubKeys(3).map(n => n.key); @@ -72,26 +71,10 @@ describe('OnionPathsErrors', () => { SNodeAPI.Onions.resetSnodeFailureCount(); - guardNodesArray = guardPubkeys.map(ed25519 => { - fakePortCurrent++; - return { - ip: fakeIP, - port: fakePortCurrent, - pubkey_ed25519: ed25519, - pubkey_x25519: ed25519, - }; - }); + guardNodesArray = guardPubkeys.map(generateFakeSnodeWithEdKey); guardSnode1 = guardNodesArray[0]; - otherNodesArray = otherNodesPubkeys.map(ed25519 => { - fakePortCurrent++; - return { - ip: fakeIP, - port: fakePortCurrent, - pubkey_ed25519: ed25519, - pubkey_x25519: ed25519, - }; - }); + otherNodesArray = otherNodesPubkeys.map(generateFakeSnodeWithEdKey); fakeSnodePool = [...guardNodesArray, ...otherNodesArray]; diff --git a/ts/test/test-utils/utils/pubkey.ts b/ts/test/test-utils/utils/pubkey.ts index f78a5109b4..62c3c7cf1f 100644 --- a/ts/test/test-utils/utils/pubkey.ts +++ b/ts/test/test-utils/utils/pubkey.ts @@ -37,7 +37,8 @@ export function generateFakePubKeys(amount: number): Array { export function generateFakeSnode(): Snode { return { - ip: '136.243.103.171', + // tslint:disable: insecure-random + ip: `136.243.${Math.random() * 255}.${Math.random() * 255}`, port: 22116, pubkey_x25519: generateFakePubKeyStr(), pubkey_ed25519: generateFakePubKeyStr(), @@ -46,7 +47,7 @@ export function generateFakeSnode(): Snode { export function generateFakeSnodeWithEdKey(ed25519Pubkey: string): Snode { return { - ip: '136.243.103.171', + ip: `136.243.${Math.random() * 255}.${Math.random() * 255}`, port: 22116, pubkey_x25519: generateFakePubKeyStr(), pubkey_ed25519: ed25519Pubkey, From 1eff39c3ba06b7cd4bf76adfa562785e7b4c3343 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 18 Oct 2021 16:48:03 +1100 Subject: [PATCH 3/5] allow removing note to self contact this does not actually remove it as we need it for our avatar and stuffs. Fixes #1973 --- ts/components/session/menu/ConversationHeaderMenu.tsx | 2 +- .../session/menu/ConversationListItemContextMenu.tsx | 2 +- ts/components/session/menu/Menu.tsx | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ts/components/session/menu/ConversationHeaderMenu.tsx b/ts/components/session/menu/ConversationHeaderMenu.tsx index 144bd42136..2832127982 100644 --- a/ts/components/session/menu/ConversationHeaderMenu.tsx +++ b/ts/components/session/menu/ConversationHeaderMenu.tsx @@ -77,7 +77,7 @@ const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)} {/* TODO: add delete group */} {getInviteContactMenuItem(isGroup, isPublic, conversationId)} - {getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, conversationId)} + {getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)} ); }; diff --git a/ts/components/session/menu/ConversationListItemContextMenu.tsx b/ts/components/session/menu/ConversationListItemContextMenu.tsx index 0796eb4527..0cf88923c0 100644 --- a/ts/components/session/menu/ConversationListItemContextMenu.tsx +++ b/ts/components/session/menu/ConversationListItemContextMenu.tsx @@ -69,7 +69,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) => {getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)} {getDeleteMessagesMenuItem(isPublic, conversationId)} {getInviteContactMenuItem(isGroup, isPublic, conversationId)} - {getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, conversationId)} + {getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)} {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)} ); diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index 0409e0ddb0..a781c8355b 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -75,14 +75,13 @@ function showCopyId(isPublic: boolean, isGroup: boolean): boolean { } function showDeleteContact( - isMe: boolean, isGroup: boolean, isPublic: boolean, isGroupLeft: boolean, isKickedFromGroup: boolean ): boolean { // you need to have left a closed group first to be able to delete it completely. - return (!isMe && !isGroup) || (isGroup && (isGroupLeft || isKickedFromGroup || isPublic)); + return !isGroup || (isGroup && (isGroupLeft || isKickedFromGroup || isPublic)); } function showAddModerators( @@ -170,7 +169,6 @@ export const getPinConversationMenuItem = (conversationId: string): JSX.Element }; export function getDeleteContactMenuItem( - isMe: boolean | undefined, isGroup: boolean | undefined, isPublic: boolean | undefined, isLeft: boolean | undefined, @@ -181,7 +179,6 @@ export function getDeleteContactMenuItem( if ( showDeleteContact( - Boolean(isMe), Boolean(isGroup), Boolean(isPublic), Boolean(isLeft), From 8446075bb1426c334c87ac001420028b1717ec59 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 18 Oct 2021 17:17:13 +1100 Subject: [PATCH 4/5] convo list click on avatar open user details Relates #1971 --- ts/components/ConversationListItem.tsx | 38 ++++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index cdb1921f38..ec667eb4f2 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -23,10 +23,11 @@ import { import _ from 'underscore'; import { useMembersAvatars } from '../hooks/useMembersAvatar'; import { SessionIcon } from './session/icon'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { SectionType } from '../state/ducks/section'; import { getFocusedSection } from '../state/selectors/section'; import { ConversationNotificationSettingType } from '../models/conversation'; +import { updateUserDetailsModal } from '../state/ducks/modalDialog'; // tslint:disable-next-line: no-empty-interface export interface ConversationListItemProps extends ReduxConversationType {} @@ -131,16 +132,15 @@ const HeaderItem = (props: { {unreadCountDiv} {atSymbol} - { -
0 ? 'module-conversation-list-item__header__date--has-unread' : null - )} - > - {} -
- } + +
0 ? 'module-conversation-list-item__header__date--has-unread' : null + )} + > + +
); }; @@ -220,10 +220,12 @@ const AvatarItem = (props: { memberAvatars?: Array; name?: string; profileName?: string; + isPrivate: boolean; }) => { - const { avatarPath, name, conversationId, profileName, memberAvatars } = props; + const { avatarPath, name, isPrivate, conversationId, profileName, memberAvatars } = props; const userName = name || profileName || conversationId; + const dispatch = useDispatch(); return (
@@ -233,6 +235,17 @@ const AvatarItem = (props: { size={AvatarSize.S} memberAvatars={memberAvatars} pubkey={conversationId} + onAvatarClick={() => { + if (isPrivate) { + dispatch( + updateUserDetailsModal({ + conversationId: conversationId, + userName, + authorAvatarPath: avatarPath, + }) + ); + } + }} />
); @@ -309,6 +322,7 @@ const ConversationListItem = (props: Props) => { memberAvatars={membersAvatar} profileName={profileName} name={name} + isPrivate={isPrivate || false} />
Date: Mon, 18 Oct 2021 17:34:13 +1100 Subject: [PATCH 5/5] link pgup and down in messages list Fixes #1919 --- .../conversation/SessionMessagesList.tsx | 12 ++++++ .../SessionMessagesListContainer.tsx | 40 ++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 5bcbafbc7c..0a1e79a4e9 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; +// tslint:disable-next-line: no-submodule-imports +import useKey from 'react-use/lib/useKey'; import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../../models/messageType'; import { PropsForExpirationTimer, @@ -19,9 +21,19 @@ import { SessionLastSeenIndicator } from './SessionLastSeenIndicator'; export const SessionMessagesList = (props: { scrollToQuoteMessage: (options: QuoteClickOptions) => Promise; + onPageUpPressed: () => void; + onPageDownPressed: () => void; }) => { const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation); + useKey('PageUp', () => { + props.onPageUpPressed(); + }); + + useKey('PageDown', () => { + props.onPageDownPressed(); + }); + return ( <> {messagesProps.map(messageProps => { diff --git a/ts/components/session/conversation/SessionMessagesListContainer.tsx b/ts/components/session/conversation/SessionMessagesListContainer.tsx index 552e003e57..fc12d0a97a 100644 --- a/ts/components/session/conversation/SessionMessagesListContainer.tsx +++ b/ts/components/session/conversation/SessionMessagesListContainer.tsx @@ -30,7 +30,7 @@ import { import { SessionMessagesList } from './SessionMessagesList'; export type SessionMessageListProps = { - messageContainerRef: React.RefObject; + messageContainerRef: React.RefObject; }; type Props = SessionMessageListProps & { @@ -82,7 +82,7 @@ class SessionMessagesListContainerInner extends React.Component { const newFirstMesssageId = this.props.messagesProps[0]?.propsForMessage.id; const messageAddedWasMoreRecentOne = prevFirstMesssageId !== newFirstMesssageId; - if (isSameConvo && snapShot?.realScrollTop && prevMsgLength !== newMsgLength) { + if (isSameConvo && snapShot?.realScrollTop && prevMsgLength !== newMsgLength && currentRef) { if (messageAddedWasMoreRecentOne) { if (snapShot.scrollHeight - snapShot.realScrollTop < 50) { // consider that we were scrolled to bottom @@ -105,13 +105,13 @@ class SessionMessagesListContainerInner extends React.Component { public getSnapshotBeforeUpdate() { const messageContainer = this.props.messageContainerRef.current; - const scrollTop = messageContainer.scrollTop; - const scrollHeight = messageContainer.scrollHeight; + const scrollTop = messageContainer?.scrollTop || undefined; + const scrollHeight = messageContainer?.scrollHeight || undefined; // as we use column-reverse for displaying message list // the top is < 0 // tslint:disable-next-line: restrict-plus-operands - const realScrollTop = scrollHeight + scrollTop; + const realScrollTop = scrollHeight && scrollTop ? scrollHeight + scrollTop : undefined; return { realScrollTop, fakeScrollTop: scrollTop, @@ -147,7 +147,11 @@ class SessionMessagesListContainerInner extends React.Component { key="typing-bubble" /> - +
@@ -239,6 +243,30 @@ class SessionMessagesListContainerInner extends React.Component { messageContainer.scrollTop = messageContainer.scrollHeight - messageContainer.clientHeight; } + private scrollPgUp() { + const messageContainer = this.props.messageContainerRef.current; + if (!messageContainer) { + return; + } + messageContainer.scrollBy({ + top: Math.floor(-messageContainer.clientHeight * 2) / 3, + behavior: 'smooth', + }); + } + + private scrollPgDown() { + const messageContainer = this.props.messageContainerRef.current; + if (!messageContainer) { + return; + } + + // tslint:disable-next-line: restrict-plus-operands + messageContainer.scrollBy({ + top: Math.floor(+messageContainer.clientHeight * 2) / 3, + behavior: 'smooth', + }); + } + private async scrollToQuoteMessage(options: QuoteClickOptions) { const { quoteAuthor, quoteId, referencedMessageNotFound } = options;