diff --git a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx index ce055d2426e6..22a27ff30e73 100644 --- a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx +++ b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx @@ -98,11 +98,12 @@ export function ChooseGroupMembersModal({ }: PropsType): JSX.Element { const [focusRef] = useRestoreFocus(); + const parsedUsername = getUsernameFromSearch(searchTerm); let username: string | undefined; let isUsernameChecked = false; let isUsernameVisible = false; if (isUsernamesEnabled) { - username = getUsernameFromSearch(searchTerm); + username = parsedUsername; isUsernameChecked = selectedContacts.some( contact => contact.username === username @@ -114,7 +115,7 @@ export function ChooseGroupMembersModal({ } let phoneNumber: ParsedE164Type | undefined; - if (!username) { + if (!parsedUsername) { phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode); } diff --git a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx index 264e9b664be3..c81d18309ed9 100644 --- a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx +++ b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx @@ -91,13 +91,13 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper contact.username !== username - ); + const username = getUsernameFromSearch(searchTerm); + const isUsernameVisible = + username !== undefined && + this.candidateContacts.every(contact => contact.username !== username); - if (isVisible) { + if (isUsernamesEnabled) { + if (isUsernameVisible) { this.username = username; } @@ -109,7 +109,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper contact.e164 === phoneNumber.e164); diff --git a/ts/components/leftPane/LeftPaneComposeHelper.tsx b/ts/components/leftPane/LeftPaneComposeHelper.tsx index 670509bb485c..b99dfb684771 100644 --- a/ts/components/leftPane/LeftPaneComposeHelper.tsx +++ b/ts/components/leftPane/LeftPaneComposeHelper.tsx @@ -68,20 +68,20 @@ export class LeftPaneComposeHelper extends LeftPaneHelper contact.username !== this.username - ); + Boolean(username) && + this.composeContacts.every(contact => contact.username !== username); } else { this.isUsernameVisible = false; } const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode); - if (!this.username && phoneNumber) { + if (!username && phoneNumber) { this.phoneNumber = phoneNumber; this.isPhoneNumberVisible = this.composeContacts.every( contact => contact.e164 !== phoneNumber.e164 diff --git a/ts/test-mock/pnp/username_test.ts b/ts/test-mock/pnp/username_test.ts index b58145ef038a..881f79ea4652 100644 --- a/ts/test-mock/pnp/username_test.ts +++ b/ts/test-mock/pnp/username_test.ts @@ -258,7 +258,7 @@ describe('pnp/username', function needsName() { await window.locator('button[aria-label="New chat"]').click(); const searchInput = window.locator('.module-SearchInput__container input'); - await searchInput.type(`@${CARL_USERNAME}`); + await searchInput.type(CARL_USERNAME); debug('starting lookup'); await window.locator(`div.ListTile >> "${CARL_USERNAME}"`).click(); diff --git a/ts/test-node/components/leftPane/LeftPaneChooseGroupMembersHelper_test.ts b/ts/test-node/components/leftPane/LeftPaneChooseGroupMembersHelper_test.ts index 410b35690c6c..40d653d8f85f 100644 --- a/ts/test-node/components/leftPane/LeftPaneChooseGroupMembersHelper_test.ts +++ b/ts/test-node/components/leftPane/LeftPaneChooseGroupMembersHelper_test.ts @@ -185,7 +185,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => { const helper = new LeftPaneChooseGroupMembersHelper({ ...defaults, candidateContacts: [], - searchTerm: 'signal', + searchTerm: 'signal.01', selectedContacts: [], }); @@ -195,7 +195,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => { ); assert.deepEqual(helper.getRow(1), { type: RowType.UsernameCheckbox, - username: 'signal', + username: 'signal.01', isChecked: false, isFetching: false, }); diff --git a/ts/test-node/components/leftPane/LeftPaneComposeHelper_test.ts b/ts/test-node/components/leftPane/LeftPaneComposeHelper_test.ts index fb047ff0b23e..79c1d397cfbc 100644 --- a/ts/test-node/components/leftPane/LeftPaneComposeHelper_test.ts +++ b/ts/test-node/components/leftPane/LeftPaneComposeHelper_test.ts @@ -88,7 +88,7 @@ describe('LeftPaneComposeHelper', () => { composeContacts: [getDefaultConversation(), getDefaultConversation()], composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()], regionCode: 'US', - searchTerm: 'someone', + searchTerm: 'someone.01', isUsernamesEnabled: true, uuidFetchState: {}, }).getRowCount(), @@ -102,7 +102,7 @@ describe('LeftPaneComposeHelper', () => { composeContacts: [getDefaultConversation(), getDefaultConversation()], composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()], regionCode: 'US', - searchTerm: 'someone', + searchTerm: 'someone.54321', isUsernamesEnabled: false, uuidFetchState: {}, }).getRowCount(), @@ -116,7 +116,7 @@ describe('LeftPaneComposeHelper', () => { composeContacts: [], composeGroups: [], regionCode: 'US', - searchTerm: 'foobar', + searchTerm: 'foobar.01', isUsernamesEnabled: true, uuidFetchState: {}, }).getRowCount(), @@ -127,7 +127,7 @@ describe('LeftPaneComposeHelper', () => { composeContacts: [getDefaultConversation(), getDefaultConversation()], composeGroups: [], regionCode: 'US', - searchTerm: 'foobar', + searchTerm: 'foobar.01', isUsernamesEnabled: true, uuidFetchState: {}, }).getRowCount(), @@ -138,7 +138,7 @@ describe('LeftPaneComposeHelper', () => { composeContacts: [getDefaultConversation(), getDefaultConversation()], composeGroups: [getDefaultGroupListItem()], regionCode: 'US', - searchTerm: 'foobar', + searchTerm: 'foobar.01', isUsernamesEnabled: true, uuidFetchState: {}, }).getRowCount(), @@ -166,7 +166,7 @@ describe('LeftPaneComposeHelper', () => { composeContacts: [], composeGroups: [], regionCode: 'US', - searchTerm: 'someone', + searchTerm: 'someone.02', isUsernamesEnabled: true, uuidFetchState: {}, }).getRowCount(), @@ -346,7 +346,7 @@ describe('LeftPaneComposeHelper', () => { }); it('returns just a "find by username" header if no results', () => { - const username = 'someone'; + const username = 'someone.02'; const helper = new LeftPaneComposeHelper({ composeContacts: [], diff --git a/ts/test-node/types/Username_test.ts b/ts/test-node/types/Username_test.ts index c2f56e1b28a4..6436caf0aabe 100644 --- a/ts/test-node/types/Username_test.ts +++ b/ts/test-node/types/Username_test.ts @@ -10,38 +10,13 @@ describe('Username', () => { const { getUsernameFromSearch } = Username; it('matches invalid username searches', () => { - assert.strictEqual(getUsernameFromSearch('use'), 'use'); - assert.strictEqual( - getUsernameFromSearch('username9012345678901234567'), - 'username9012345678901234567' - ); + assert.isUndefined(getUsernameFromSearch('use')); + assert.isUndefined(getUsernameFromSearch('username9012345678901234567')); }); it('matches valid username searches', () => { - assert.strictEqual(getUsernameFromSearch('username_34'), 'username_34'); - assert.strictEqual(getUsernameFromSearch('u5ername'), 'u5ername'); assert.strictEqual(getUsernameFromSearch('username.12'), 'username.12'); - assert.strictEqual(getUsernameFromSearch('user'), 'user'); - assert.strictEqual( - getUsernameFromSearch('username901234567890123456'), - 'username901234567890123456' - ); - }); - - it('matches valid and invalid usernames with @ prefix', () => { - assert.strictEqual(getUsernameFromSearch('@username!'), 'username!'); - assert.strictEqual(getUsernameFromSearch('@1username'), '1username'); - assert.strictEqual(getUsernameFromSearch('@username_34'), 'username_34'); - assert.strictEqual(getUsernameFromSearch('@username.34'), 'username.34'); - assert.strictEqual(getUsernameFromSearch('@u5ername'), 'u5ername'); - }); - - it('matches valid and invalid usernames with @ suffix', () => { - assert.strictEqual(getUsernameFromSearch('username!@'), 'username!'); - assert.strictEqual(getUsernameFromSearch('1username@'), '1username'); - assert.strictEqual(getUsernameFromSearch('username_34@'), 'username_34'); - assert.strictEqual(getUsernameFromSearch('username.34@'), 'username.34'); - assert.strictEqual(getUsernameFromSearch('u5ername@'), 'u5ername'); + assert.strictEqual(getUsernameFromSearch('xyz.568'), 'xyz.568'); }); it('does not match something that looks like a phone number', () => { diff --git a/ts/types/Username.ts b/ts/types/Username.ts index 26347f55b4e2..51366f522b1c 100644 --- a/ts/types/Username.ts +++ b/ts/types/Username.ts @@ -1,6 +1,8 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { usernames } from '@signalapp/libsignal-client'; + export type UsernameReservationType = Readonly<{ username: string; previousUsername: string | undefined; @@ -24,23 +26,12 @@ export enum ConfirmUsernameResult { } export function getUsernameFromSearch(searchTerm: string): string | undefined { - // Search term contains username if it: - // - Is a valid username with or without a discriminator - // - Starts with @ - // - Ends with @ - const match = searchTerm.match( - /^(?:(?[a-z_][0-9a-z_]*(?:\.\d*)?)|@(?.*?)@?|@?(?.*?)?@)$/ - ); - if (!match) { + try { + usernames.hash(searchTerm); + return searchTerm; + } catch { return undefined; } - - const { groups } = match; - if (!groups) { - return undefined; - } - - return (groups.valid || groups.start || groups.end) ?? undefined; } export function getNickname(username: string): string | undefined {