From e164bd9d5a97331a65e21e84055393e35de79ae1 Mon Sep 17 00:00:00 2001 From: Ian Macdonald Date: Sat, 17 Sep 2022 12:29:22 +0200 Subject: [PATCH 01/17] Retitle left panel 'Conversations', since that's what it displays. --- _locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index af96d001f..35af33c2c 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -71,7 +71,7 @@ "noSearchResults": "No results found for \"$searchTerm$\"", "conversationsHeader": "Contacts and Groups", "contactsHeader": "Contacts", - "messagesHeader": "Messages", + "messagesHeader": "Conversations", "settingsHeader": "Settings", "typingAlt": "Typing animation for this conversation", "contactAvatarAlt": "Avatar for contact $name$", From ca3da7a603c449c609d79e1bee6022df0ccf2079 Mon Sep 17 00:00:00 2001 From: Ian Macdonald Date: Mon, 10 Oct 2022 21:13:06 +0200 Subject: [PATCH 02/17] Set the Windows spell-checking language from $LANGUAGE. Session on Windows wants to spell-check using American English, no matter what I do. This seems to be because it wrongly assumes my locale to be `en-US`, when it is actually `en-GB`. ``` "spellcheck: setting languages to: [\"en-US\"]","time":"2022-10-10T19:19:09.216Z" ``` With this patch, Windows will use `$LANGUAGE`, if set, to determine the language to be used for spell-checking. Linux is unaffected by this patch and will correctly infer the spell-checking language from `$LANG`. Create a .bat file and start Session from this: ``` @echo off set LANGUAGE=en-GB "C:\Users\ian\AppData\Local\Programs\Session\Session.exe" --lang=en-GB ``` Note that the use of `--lang=` above sets only the Session UI language, not the spell-checker language. This is a partial fix for #2013, specifically [this comment](https://github.com/oxen-io/session-desktop/issues/2013#issuecomment-998679090): --- ts/node/spell_check.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts/node/spell_check.ts b/ts/node/spell_check.ts index 42dc04eae..00f73afe8 100644 --- a/ts/node/spell_check.ts +++ b/ts/node/spell_check.ts @@ -5,7 +5,9 @@ import { sync as osLocaleSync } from 'os-locale'; export const setup = (browserWindow: BrowserWindow, messages: any) => { const { session } = browserWindow.webContents; - const userLocale = osLocaleSync().replace(/_/g, '-'); + const userLocale = process.env.LANGUAGE + ? process.env.LANGUAGE + : osLocaleSync().replace(/_/g, '-'); const userLocales = [userLocale, userLocale.split('-')[0]]; const available = session.availableSpellCheckerLanguages; From e3006ae157a87b210e48a92ab0889b763f51bf98 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 17 Oct 2022 17:15:08 +1100 Subject: [PATCH 03/17] fix: do not return updated messages from markAllRead if not needed --- ts/data/data.ts | 8 +++-- ts/interactions/conversationInteractions.ts | 4 --- ts/models/conversation.ts | 36 ++++++++++++++------- ts/node/sql.ts | 31 ++++++++++-------- ts/state/ducks/conversations.ts | 12 +++++++ 5 files changed, 60 insertions(+), 31 deletions(-) diff --git a/ts/data/data.ts b/ts/data/data.ts index 9a33e41f6..82017155e 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -493,9 +493,13 @@ async function getUnreadByConversation(conversationId: string): Promise> { - const messagesIds = await channels.markAllAsReadByConversationNoExpiration(conversationId); + const messagesIds = await channels.markAllAsReadByConversationNoExpiration( + conversationId, + returnMessagesUpdated + ); return messagesIds; } diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index bb7059e56..6c13eafa9 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -254,11 +254,7 @@ export function showLeaveGroupByConvoId(conversationId: string) { export function showInviteContactByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateInviteContactModal({ conversationId })); } -export async function onMarkAllReadByConvoId(conversationId: string) { - const conversation = getConversationController().get(conversationId); - await conversation.markReadBouncy(Date.now()); -} export function showAddModeratorsByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateAddModeratorsModal({ conversationId })); diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index fe3942186..266629ee6 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -32,6 +32,7 @@ import { actions as conversationActions, conversationChanged, conversationsChanged, + markConversationFullyRead, MessageModelPropsWithoutConvoProps, ReduxConversationType, } from '../state/ducks/conversations'; @@ -1231,27 +1232,38 @@ export class ConversationModel extends Backbone.Model { * Send read receipt if needed. */ public async markAllAsRead() { - if (this.isOpenGroupV2()) { + /** + * when marking all as read, there is a bunch of things we need to do. + * - we need to update all the messages in the DB not read yet for that conversation + * - we need to send the read receipts if there is one needed for those messages + * - we need to trigger a change on the redux store, so those messages are read AND mark the whole convo as read. + * - we need to remove any notifications related to this conversation ID. + * + * + * (if there is an expireTimer, we do it the slow way, handling each message separately) + */ + const expireTimerSet = !!this.get('expireTimer'); + if (this.isOpenGroupV2() || !expireTimerSet) { // for opengroups, we batch everything as there is no expiration timer to take care (and potentially a lot of messages) - await Data.markAllAsReadByConversationNoExpiration(this.id); + const isOpenGroup = this.isOpenGroupV2(); + // if this is an opengroup there is no need to send read receipt, and so no need to fetch messages updated. + const allReadMessages = await Data.markAllAsReadByConversationNoExpiration( + this.id, + !isOpenGroup + ); this.set({ mentionedUs: false, unreadCount: 0 }); await this.commit(); - return; - } - - // if the conversation has no expiration timer, we can also batch everything, but we also need to send read receipts potentially - // so we grab them from the db - if (!this.get('expireTimer')) { - const allReadMessages = await Data.markAllAsReadByConversationNoExpiration(this.id); - this.set({ mentionedUs: false, unreadCount: 0 }); - await this.commit(); - if (allReadMessages.length) { + if (!this.isOpenGroupV2() && allReadMessages.length) { await this.sendReadReceiptsIfNeeded(uniq(allReadMessages)); } + Notifications.clearByConversationID(this.id); + window.inboxStore?.dispatch(markConversationFullyRead(this.id)); + return; } + // otherwise, do it the slow way await this.markReadBouncy(Date.now()); } diff --git a/ts/node/sql.ts b/ts/node/sql.ts index f7270e7f7..33ae61980 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -1117,18 +1117,23 @@ function getUnreadByConversation(conversationId: string) { * Warning: This does not start expiration timer */ function markAllAsReadByConversationNoExpiration( - conversationId: string -): Array<{ id: string; timestamp: number }> { - const messagesUnreadBefore = assertGlobalInstance() - .prepare( - `SELECT json FROM ${MESSAGES_TABLE} WHERE - unread = $unread AND - conversationId = $conversationId;` - ) - .all({ - unread: 1, - conversationId, - }); + conversationId: string, + returnMessagesUpdated: boolean +): Array { + let toReturn: Array = []; + if (returnMessagesUpdated) { + const messagesUnreadBefore = assertGlobalInstance() + .prepare( + `SELECT json FROM ${MESSAGES_TABLE} WHERE + unread = $unread AND + conversationId = $conversationId;` + ) + .all({ + unread: 1, + conversationId, + }); + toReturn = compact(messagesUnreadBefore.map(row => jsonToObject(row.json).sent_at)); + } assertGlobalInstance() .prepare( @@ -1142,7 +1147,7 @@ function markAllAsReadByConversationNoExpiration( conversationId, }); - return compact(messagesUnreadBefore.map(row => jsonToObject(row.json).sent_at)); + return toReturn; } function getUnreadCountByConversation(conversationId: string) { diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 3d4614d4b..6c2dbd28c 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -699,11 +699,23 @@ const conversationsSlice = createSlice({ return state; } + let updatedMessages = state.messages; + + // if some are unread, mark them as read + if (state.messages.some(m => m.propsForMessage.isUnread)) { + updatedMessages = state.messages.map(m => ({ + ...m, + propsForMessage: { ...m.propsForMessage, isUnread: false }, + })); + } + // keep the unread visible just like in other apps. It will be shown until the user changes convo return { ...state, shouldHighlightMessage: false, firstUnreadMessageId: undefined, + + messages: updatedMessages, }; }, /** From 0b8a10ad66030ae820718da0db9f58dc18737696 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 18 Oct 2022 13:54:27 +1100 Subject: [PATCH 04/17] fix: improve markAllAsRead performances - make an index on unread+convoId - make the message update trigger only run when the body changed --- ts/data/data.ts | 6 +++++- ts/node/database_utility.ts | 2 +- ts/node/migration/sessionMigrations.ts | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ts/data/data.ts b/ts/data/data.ts index 82017155e..8374850c4 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -494,12 +494,16 @@ async function getUnreadByConversation(conversationId: string): Promise> { + // tslint:disable-next-line: no-console + console.time('markAllAsReadByConversationNoExpiration'); const messagesIds = await channels.markAllAsReadByConversationNoExpiration( conversationId, returnMessagesUpdated ); + // tslint:disable-next-line: no-console + console.timeEnd('markAllAsReadByConversationNoExpiration'); return messagesIds; } diff --git a/ts/node/database_utility.ts b/ts/node/database_utility.ts index 7a77eca44..17039750e 100644 --- a/ts/node/database_utility.ts +++ b/ts/node/database_utility.ts @@ -264,7 +264,7 @@ export function rebuildFtsTable(db: BetterSqlite3.Database) { CREATE TRIGGER messages_on_delete AFTER DELETE ON ${MESSAGES_TABLE} BEGIN DELETE FROM ${MESSAGES_FTS_TABLE} WHERE id = old.id; END; - CREATE TRIGGER messages_on_update AFTER UPDATE ON ${MESSAGES_TABLE} BEGIN + CREATE TRIGGER messages_on_update AFTER UPDATE ON ${MESSAGES_TABLE} WHEN new.body <> old.body BEGIN DELETE FROM ${MESSAGES_FTS_TABLE} WHERE id = old.id; INSERT INTO ${MESSAGES_FTS_TABLE}( id, diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts index f6574f141..8c2ed8b40 100644 --- a/ts/node/migration/sessionMigrations.ts +++ b/ts/node/migration/sessionMigrations.ts @@ -78,6 +78,7 @@ const LOKI_SCHEMA_VERSIONS = [ updateToSessionSchemaVersion26, updateToSessionSchemaVersion27, updateToSessionSchemaVersion28, + updateToSessionSchemaVersion29, ]; function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) { @@ -1181,6 +1182,28 @@ function updateToSessionSchemaVersion28(currentVersion: number, db: BetterSqlite console.log(`updateToSessionSchemaVersion${targetVersion}: success!`); } +function updateToSessionSchemaVersion29(currentVersion: number, db: BetterSqlite3.Database) { + const targetVersion = 29; + if (currentVersion >= targetVersion) { + return; + } + + console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`); + + db.transaction(() => { + dropFtsAndTriggers(db); + db.exec(`CREATE INDEX messages_unread_by_conversation ON ${MESSAGES_TABLE} ( + unread, + conversationId + );`); + rebuildFtsTable(db); + // Keeping this empty migration because some people updated to this already, even if it is not needed anymore + writeSessionSchemaVersion(targetVersion, db); + })(); + + console.log(`updateToSessionSchemaVersion${targetVersion}: success!`); +} + // function printTableColumns(table: string, db: BetterSqlite3.Database) { // console.info(db.pragma(`table_info('${table}');`)); // } From 07dfdb3350b79906bb0709cdb507a85cb2bf0fb9 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 18 Oct 2022 17:46:12 +1100 Subject: [PATCH 05/17] fix: remove spinner of first poll of sogs even when not selected --- .../open_group_api/opengroupV2/OpenGroupServerPoller.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts index 99e925faa..0ffafa19e 100644 --- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts +++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts @@ -322,6 +322,8 @@ export class OpenGroupServerPoller { // ==> At this point all those results need to trigger conversation updates, so update what we have to update await handleBatchPollResults(this.serverUrl, batchPollResults, subrequestOptions); + // this is very hacky but is needed to remove the spinner of an opengroup conversation while it loads the first patch of messages. + // Absolutely not the react way, but well. for (const room of subrequestOptions) { if (room.type === 'messages' && !room.messages?.sinceSeqNo && room.messages?.roomId) { const conversationKey = getOpenGroupV2ConversationId( @@ -346,6 +348,13 @@ export class OpenGroupServerPoller { }) ); }); + } else { + window.inboxStore?.dispatch( + markConversationInitialLoadingInProgress({ + conversationKey, + isInitialFetchingInProgress: false, + }) + ); } } }, 5000); From 3c17d5cc05726d868d736adc16233a94cc1adb0a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 18 Oct 2022 17:46:28 +1100 Subject: [PATCH 06/17] chore: bump to Session 1.10.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 241029303..1af816b57 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "session-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "version": "1.10.2", + "version": "1.10.3", "license": "GPL-3.0", "author": { "name": "Oxen Labs", From 1b4d35a32480e55b9b815cbdd25628be3441ed72 Mon Sep 17 00:00:00 2001 From: William Grant Date: Wed, 19 Oct 2022 13:08:15 +1100 Subject: [PATCH 07/17] fix: add shadow to session scroll button for better visibility --- ts/components/SessionScrollButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/components/SessionScrollButton.tsx b/ts/components/SessionScrollButton.tsx index 94606b593..df79228e3 100644 --- a/ts/components/SessionScrollButton.tsx +++ b/ts/components/SessionScrollButton.tsx @@ -13,6 +13,7 @@ const SessionScrollButtonDiv = styled.div` .session-icon-button { background-color: var(--message-bubbles-received-background-color); + box-shadow: var(--drop-shadow); } `; From ddfaad5220c1f828c8dd077c86289267686e9c28 Mon Sep 17 00:00:00 2001 From: William Grant Date: Wed, 19 Oct 2022 13:28:13 +1100 Subject: [PATCH 08/17] fix: added a unique shadow variable for the scroll button --- ts/components/SessionScrollButton.tsx | 2 +- ts/interactions/conversationInteractions.ts | 1 - ts/themes/globals.tsx | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ts/components/SessionScrollButton.tsx b/ts/components/SessionScrollButton.tsx index df79228e3..4019faaf2 100644 --- a/ts/components/SessionScrollButton.tsx +++ b/ts/components/SessionScrollButton.tsx @@ -13,7 +13,7 @@ const SessionScrollButtonDiv = styled.div` .session-icon-button { background-color: var(--message-bubbles-received-background-color); - box-shadow: var(--drop-shadow); + box-shadow: var(--scroll-button-shadow); } `; diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 6c13eafa9..6b4cd2898 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -255,7 +255,6 @@ export function showInviteContactByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateInviteContactModal({ conversationId })); } - export function showAddModeratorsByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateAddModeratorsModal({ conversationId })); } diff --git a/ts/themes/globals.tsx b/ts/themes/globals.tsx index 98760c6b3..4c1bb5022 100644 --- a/ts/themes/globals.tsx +++ b/ts/themes/globals.tsx @@ -55,6 +55,7 @@ export type ThemeGlobals = { '--shadow-color': string; '--drop-shadow': string; '--context-menu-shadow-color': string; + '--scroll-button-shadow': string; /* Path Button */ '--button-path-default-color': string; @@ -128,13 +129,14 @@ export const THEME_GLOBALS: ThemeGlobals = { '--shadow-color': 'var(--black-color)', '--drop-shadow': '0 0 4px 0 var(--shadow-color)', '--context-menu-shadow-color': `rgba(${hexColorToRGB(COLORS.BLACK)}, 0.22)`, + '--scroll-button-shadow': `0 0 7px 0 rgba(${hexColorToRGB(COLORS.BLACK)}, 0.5)`, '--button-path-default-color': COLORS.PATH.DEFAULT, '--button-path-connecting-color': COLORS.PATH.CONNECTING, '--button-path-error-color': COLORS.PATH.ERROR, '--modal-background-color': `rgba(${hexColorToRGB(COLORS.BLACK)}, 0.3)`, - '--modal-drop-shadow': `0px 0px 10px rgba(${hexColorToRGB(COLORS.BLACK)}, 0.22)`, + '--modal-drop-shadow': `0px 0px 10px rgba(${hexColorToRGB(COLORS.BLACK)}, 0.22)`, '--lightbox-background-color': `rgba(${hexColorToRGB(COLORS.BLACK)}, 0.8)`, '--lightbox-caption-background-color': 'rgba(192, 192, 192, .40)', From 9557fdd69ca5fc310bce27d3991571b8e0bf92ff Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 19 Oct 2022 11:40:34 -0300 Subject: [PATCH 09/17] Fix .deb Section Currently the deb has `Section: default`, which isn't a valid debian section at all; this fixes it to be `net` to properly categorize it in deb package managers. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1af816b57..67d1d3005 100644 --- a/package.json +++ b/package.json @@ -306,7 +306,8 @@ "libnss3", "libasound2", "libxss1" - ] + ], + "packageCategory": "net" }, "files": [ "package.json", From 0ba0abfcdc976af9721ee80d7a199a7693468b0d Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 21 Oct 2022 13:27:56 +1100 Subject: [PATCH 10/17] fix: padding with only one digit in unread notification count --- ts/components/icon/SessionNotificationCount.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ts/components/icon/SessionNotificationCount.tsx b/ts/components/icon/SessionNotificationCount.tsx index 580586cfc..b4102bf23 100644 --- a/ts/components/icon/SessionNotificationCount.tsx +++ b/ts/components/icon/SessionNotificationCount.tsx @@ -7,12 +7,10 @@ type Props = { const StyledCountContainer = styled.div<{ shouldRender: boolean }>` position: absolute; - width: 24px; - height: 12px; font-size: 18px; top: 27px; right: 8px; - padding: 3px; + padding: 0 6px; opacity: 1; display: flex; align-items: center; From 0b9f1a494a2323e12ddbed10b06cca82ffe12306 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 21 Oct 2022 13:39:47 +1100 Subject: [PATCH 11/17] fix: allow 99 unread count before going to 99+ --- ts/components/icon/SessionNotificationCount.tsx | 6 +++--- ts/components/leftpane/ActionsPanel.tsx | 2 -- ts/state/selectors/conversations.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ts/components/icon/SessionNotificationCount.tsx b/ts/components/icon/SessionNotificationCount.tsx index b4102bf23..99da0b924 100644 --- a/ts/components/icon/SessionNotificationCount.tsx +++ b/ts/components/icon/SessionNotificationCount.tsx @@ -27,19 +27,19 @@ const StyledCountContainer = styled.div<{ shouldRender: boolean }>` const StyledCount = styled.div` position: relative; - font-size: 0.6em; + font-size: 0.6rem; `; export const SessionNotificationCount = (props: Props) => { const { count } = props; - const overflow = Boolean(count && count > 9); + const overflow = Boolean(count && count > 99); const shouldRender = Boolean(count && count > 0); if (overflow) { return ( - {9} + {99} + diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx index 12f0ed8b4..e71510f3a 100644 --- a/ts/components/leftpane/ActionsPanel.tsx +++ b/ts/components/leftpane/ActionsPanel.tsx @@ -117,7 +117,6 @@ const Section = (props: { type: SectionType }) => { iconSize="medium" dataTestId="settings-section" iconType={'gear'} - notificationCount={unreadToShow} onClick={handleClick} isSelected={isSelected} /> @@ -138,7 +137,6 @@ const Section = (props: { type: SectionType }) => { iconSize="medium" iconType={isDarkMode ? 'moon' : 'sun'} dataTestId="theme-section" - notificationCount={unreadToShow} onClick={handleClick} isSelected={isSelected} /> diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 62510df09..563db45f8 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -364,7 +364,7 @@ export const _getLeftPaneLists = ( } if ( - unreadCount < 9 && + unreadCount < 100 && conversation.unreadCount && conversation.unreadCount > 0 && conversation.currentNotificationSetting !== 'disabled' From ae51b0cd96607d40c6cda37909b66478c7bc6f7f Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 21 Oct 2022 10:17:15 +1100 Subject: [PATCH 12/17] fix: include auth sogs headers everywhere --- .../sogsv3/sogsV3Capabilities.ts | 6 +-- .../open_group_api/sogsv3/sogsV3FetchFile.ts | 40 ++++++++++--------- .../open_group_api/sogsv3/sogsV3RoomInfos.ts | 4 +- ts/session/apis/snode_api/onions.ts | 7 ++++ ts/session/onions/onionSend.ts | 18 ++++----- ts/session/onions/onionv4.ts | 12 ++++++ ts/updater/updater.ts | 8 +++- 7 files changed, 58 insertions(+), 37 deletions(-) diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3Capabilities.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3Capabilities.ts index b7f4ae1d8..7e790e78f 100644 --- a/ts/session/apis/open_group_api/sogsv3/sogsV3Capabilities.ts +++ b/ts/session/apis/open_group_api/sogsv3/sogsV3Capabilities.ts @@ -5,7 +5,7 @@ import { OpenGroupData, OpenGroupV2Room } from '../../../../data/opengroups'; import AbortController, { AbortSignal } from 'abort-controller'; import { batchGlobalIsSuccess } from './sogsV3BatchPoll'; -export const capabilitiesFetchForServer = async ( +const capabilitiesFetchForServer = async ( serverUrl: string, serverPubKey: string, abortSignal: AbortSignal @@ -13,7 +13,8 @@ export const capabilitiesFetchForServer = async ( const endpoint = '/capabilities'; const method = 'GET'; const serverPubkey = serverPubKey; - const blinded = false; // for capabilities, blinding is always false as the request will fail if the server requires blinding + // for the capabilities call, we require blinded to be ON now. A sogs with blinding disabled will still allow this call and verify the blinded signature + const blinded = true; const capabilityHeaders = await OpenGroupPollingUtils.getOurOpenGroupHeaders( serverPubkey, endpoint, @@ -33,7 +34,6 @@ export const capabilitiesFetchForServer = async ( serverPubkey, serverUrl, stringifiedBody: null, - doNotIncludeOurSogsHeaders: true, // the first capabilities needs to not have any authentification to pass on a blinding-required sogs, headers: null, throwErrors: false, }); diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts index 3d0703fb8..b5a561730 100644 --- a/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts +++ b/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts @@ -1,6 +1,10 @@ import AbortController, { AbortSignal } from 'abort-controller'; import { isUndefined, toNumber } from 'lodash'; -import { OpenGroupV2Room, OpenGroupV2RoomWithImageID } from '../../../../data/opengroups'; +import { + OpenGroupData, + OpenGroupV2Room, + OpenGroupV2RoomWithImageID, +} from '../../../../data/opengroups'; import { MIME } from '../../../../types'; import { processNewAttachment } from '../../../../types/MessageAttachment'; import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface'; @@ -16,7 +20,6 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: { serverPubkey: string; blinded: boolean; abortSignal: AbortSignal; - doNotIncludeOurSogsHeaders?: boolean; headers: Record | null; roomId: string; fileId: string; @@ -28,7 +31,6 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: { blinded, abortSignal, headers: includedHeaders, - doNotIncludeOurSogsHeaders, roomId, fileId, throwError, @@ -41,15 +43,13 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: { throw new Error('endpoint needs a leading /'); } const builtUrl = new URL(`${serverUrl}${endpoint}`); - let headersWithSogsHeadersIfNeeded = doNotIncludeOurSogsHeaders - ? {} - : await OpenGroupPollingUtils.getOurOpenGroupHeaders( - serverPubkey, - endpoint, - method, - blinded, - stringifiedBody - ); + let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders( + serverPubkey, + endpoint, + method, + blinded, + stringifiedBody + ); if (isUndefined(headersWithSogsHeadersIfNeeded)) { return null; @@ -98,11 +98,14 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith return; } + const room = OpenGroupData.getV2OpenGroupRoom(convoId); + const blinded = roomHasBlindEnabled(room); + // make sure this runs only once for each rooms. - // we don't want to trigger one of those on each setPollInfo resultsas it happens on each batch poll. + // we don't want to trigger one of those on each setPollInfo results as it happens on each batch poll. const oneAtAtimeResult = (await allowOnlyOneAtATime( `sogsV3FetchPreview-${serverUrl}-${roomId}`, - () => sogsV3FetchPreview(roomInfos) + () => sogsV3FetchPreview(roomInfos, blinded) )) as Uint8Array | null; // force the return type as allowOnlyOneAtATime does not keep it if (!oneAtAtimeResult || !oneAtAtimeResult?.byteLength) { @@ -139,7 +142,7 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith * @returns the fetchedData in base64 */ export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithImageID) { - const fetched = await sogsV3FetchPreview(roomInfos); + const fetched = await sogsV3FetchPreview(roomInfos, true); // left pane are session official default rooms, which do require blinded if (fetched && fetched.byteLength) { return callUtilsWorker('arrayBufferToStringBase64', fetched); } @@ -155,7 +158,8 @@ export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithIma * Those default rooms do not have a conversation associated with them, as they are not joined yet */ const sogsV3FetchPreview = async ( - roomInfos: OpenGroupV2RoomWithImageID + roomInfos: OpenGroupV2RoomWithImageID, + blinded: boolean ): Promise => { if (!roomInfos || !roomInfos.imageID) { return null; @@ -164,11 +168,10 @@ const sogsV3FetchPreview = async ( // not a batch call yet as we need to exclude headers for this call for now const fetched = await fetchBinaryFromSogsWithOnionV4({ abortSignal: new AbortController().signal, - blinded: false, + blinded, headers: null, serverPubkey: roomInfos.serverPublicKey, serverUrl: roomInfos.serverUrl, - doNotIncludeOurSogsHeaders: true, roomId: roomInfos.roomId, fileId: roomInfos.imageID, throwError: false, @@ -198,7 +201,6 @@ export const sogsV3FetchFileByFileID = async ( headers: null, serverPubkey: roomInfos.serverPublicKey, serverUrl: roomInfos.serverUrl, - doNotIncludeOurSogsHeaders: true, roomId: roomInfos.roomId, fileId, throwError: true, diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3RoomInfos.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3RoomInfos.ts index af0657986..59a452bbe 100644 --- a/ts/session/apis/open_group_api/sogsv3/sogsV3RoomInfos.ts +++ b/ts/session/apis/open_group_api/sogsv3/sogsV3RoomInfos.ts @@ -11,7 +11,7 @@ import { export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => { const result = await OnionSending.sendJsonViaOnionV4ToSogs({ - blinded: false, + blinded: true, endpoint: '/rooms', method: 'GET', serverPubkey: roomInfos.serverPublicKey, @@ -19,7 +19,6 @@ export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => { abortSignal: new AbortController().signal, serverUrl: roomInfos.serverUrl, headers: null, - doNotIncludeOurSogsHeaders: true, throwErrors: false, }); @@ -91,7 +90,6 @@ export async function openGroupV2GetRoomInfoViaOnionV4({ stringifiedBody: null, serverPubkey, headers: null, - doNotIncludeOurSogsHeaders: true, throwErrors: false, }); const room = result?.body as Record | undefined; diff --git a/ts/session/apis/snode_api/onions.ts b/ts/session/apis/snode_api/onions.ts index f7a471923..e0aab38cb 100644 --- a/ts/session/apis/snode_api/onions.ts +++ b/ts/session/apis/snode_api/onions.ts @@ -338,6 +338,13 @@ async function processAnyOtherErrorOnPath( if (status !== 200) { window?.log?.warn(`[path] Got status: ${status}`); + if (status === 404 || status === 400) { + window?.log?.warn( + 'processAnyOtherErrorOnPathgot 404 or 400, probably a dead sogs. Skipping bad path update' + ); + return; + } + // If we have a specific node in fault we can exclude just this node. if (ciphertext?.startsWith(NEXT_NODE_NOT_FOUND_PREFIX)) { const nodeNotFound = ciphertext.substr(NEXT_NODE_NOT_FOUND_PREFIX.length); diff --git a/ts/session/onions/onionSend.ts b/ts/session/onions/onionSend.ts index f5fe4759c..a678bb4fd 100644 --- a/ts/session/onions/onionSend.ts +++ b/ts/session/onions/onionSend.ts @@ -277,7 +277,6 @@ async function sendJsonViaOnionV4ToSogs(sendOptions: { method: string; stringifiedBody: string | null; abortSignal: AbortSignal; - doNotIncludeOurSogsHeaders?: boolean; headers: Record | null; throwErrors: boolean; }): Promise { @@ -290,22 +289,19 @@ async function sendJsonViaOnionV4ToSogs(sendOptions: { stringifiedBody, abortSignal, headers: includedHeaders, - doNotIncludeOurSogsHeaders, throwErrors, } = sendOptions; if (!endpoint.startsWith('/')) { throw new Error('endpoint needs a leading /'); } const builtUrl = new URL(`${serverUrl}${endpoint}`); - let headersWithSogsHeadersIfNeeded = doNotIncludeOurSogsHeaders - ? {} - : await OpenGroupPollingUtils.getOurOpenGroupHeaders( - serverPubkey, - endpoint, - method, - blinded, - stringifiedBody - ); + let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders( + serverPubkey, + endpoint, + method, + blinded, + stringifiedBody + ); if (!headersWithSogsHeadersIfNeeded) { return null; diff --git a/ts/session/onions/onionv4.ts b/ts/session/onions/onionv4.ts index b7ec3c348..170dfa213 100644 --- a/ts/session/onions/onionv4.ts +++ b/ts/session/onions/onionv4.ts @@ -92,6 +92,18 @@ const decodeV4Response = (snodeResponse: SnodeResponseV4): DecodedResponseV4 | u break; case 'application/octet-stream': break; + case 'text/html; charset=utf-8': + try { + window?.log?.warn( + 'decodeV4Response - received raw body of type "text/html; charset=utf-8": ', + to_string(bodyBinary) + ); + } catch (e) { + window?.log?.warn( + 'decodeV4Response - received raw body of type "text/html; charset=utf-8" but not a string' + ); + } + break; default: window?.log?.warn( 'decodeV4Response - No or unknown content-type information for response: ', diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index c2255655f..a673a3c4a 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -46,7 +46,13 @@ export async function start( }, 1000 * 60 * 10); // trigger and try to update every 10 minutes to let the file gets downloaded if we are updating stopped = false; - await checkForUpdates(getMainWindow, messages, logger); + global.setTimeout(async () => { + try { + await checkForUpdates(getMainWindow, messages, logger); + } catch (error) { + logger.error('auto-update: error:', getPrintableError(error)); + } + }, 2 * 60 * 1000); // we do checks from the fileserver every 1 minute. } export function stop() { From 37cedaf24abbc2cb3d97562bd2951399ec4b226b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 24 Oct 2022 16:21:57 +1100 Subject: [PATCH 13/17] fix: make allowOneAtATime take a generic --- .../open_group_api/opengroupV2/ApiUtil.ts | 49 +++++++++---------- .../opengroupV2/OpenGroupManagerV2.ts | 2 +- .../open_group_api/sogsv3/sogsV3FetchFile.ts | 4 +- ts/session/apis/seed_node_api/SeedNodeAPI.ts | 4 +- ts/session/utils/Promise.ts | 8 +-- 5 files changed, 31 insertions(+), 36 deletions(-) diff --git a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts index 145339079..27567233c 100644 --- a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts +++ b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts @@ -142,36 +142,33 @@ const defaultServerPublicKey = 'a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f4 const defaultRoom = `${defaultServer}/main?public_key=${defaultServerPublicKey}`; const loadDefaultRoomsSingle = () => - allowOnlyOneAtATime( - 'loadDefaultRoomsSingle', - async (): Promise> => { - const roomInfos = parseOpenGroupV2(defaultRoom); - if (roomInfos) { - try { - const roomsGot = await getAllRoomInfos(roomInfos); - - if (!roomsGot) { - return []; - } - - return roomsGot.map(room => { - return { - ...room, - completeUrl: getCompleteUrlFromRoom({ - serverUrl: roomInfos.serverUrl, - serverPublicKey: roomInfos.serverPublicKey, - roomId: room.id, - }), - }; - }); - } catch (e) { - window?.log?.warn('loadDefaultRoomloadDefaultRoomssIfNeeded failed', e); + allowOnlyOneAtATime('loadDefaultRoomsSingle', async () => { + const roomInfos = parseOpenGroupV2(defaultRoom); + if (roomInfos) { + try { + const roomsGot = await getAllRoomInfos(roomInfos); + + if (!roomsGot) { + return []; } - return []; + + return roomsGot.map(room => { + return { + ...room, + completeUrl: getCompleteUrlFromRoom({ + serverUrl: roomInfos.serverUrl, + serverPublicKey: roomInfos.serverPublicKey, + roomId: room.id, + }), + }; + }); + } catch (e) { + window?.log?.warn('loadDefaultRoomloadDefaultRoomssIfNeeded failed', e); } return []; } - ); + return []; + }); /** * Load to the cache all the details of the room of the default opengroupv2 server diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts index c6a6e4ac5..07cea658a 100644 --- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts +++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupManagerV2.ts @@ -45,7 +45,7 @@ export class OpenGroupManagerV2 { serverUrl: string, roomId: string, publicKey: string - ): Promise { + ): Promise { const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${serverUrl}${roomId}`; return allowOnlyOneAtATime(oneAtaTimeStr, async () => { return this.attemptConnectionV2(serverUrl, roomId, publicKey); diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts index b5a561730..488ad5d61 100644 --- a/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts +++ b/ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts @@ -103,10 +103,10 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith // make sure this runs only once for each rooms. // we don't want to trigger one of those on each setPollInfo results as it happens on each batch poll. - const oneAtAtimeResult = (await allowOnlyOneAtATime( + const oneAtAtimeResult = await allowOnlyOneAtATime( `sogsV3FetchPreview-${serverUrl}-${roomId}`, () => sogsV3FetchPreview(roomInfos, blinded) - )) as Uint8Array | null; // force the return type as allowOnlyOneAtATime does not keep it + ); if (!oneAtAtimeResult || !oneAtAtimeResult?.byteLength) { window?.log?.warn('sogsV3FetchPreviewAndSaveIt failed for room: ', roomId); diff --git a/ts/session/apis/seed_node_api/SeedNodeAPI.ts b/ts/session/apis/seed_node_api/SeedNodeAPI.ts index 91fb75628..12231421e 100644 --- a/ts/session/apis/seed_node_api/SeedNodeAPI.ts +++ b/ts/session/apis/seed_node_api/SeedNodeAPI.ts @@ -146,9 +146,7 @@ export interface SnodeFromSeed { } const getSnodeListFromSeednodeOneAtAtime = async (seedNodes: Array) => - allowOnlyOneAtATime('getSnodeListFromSeednode', () => - getSnodeListFromSeednode(seedNodes) - ) as Promise>; + allowOnlyOneAtATime('getSnodeListFromSeednode', () => getSnodeListFromSeednode(seedNodes)); /** * This call will try 4 times to contact a seed nodes (random) and get the snode list from it. diff --git a/ts/session/utils/Promise.ts b/ts/session/utils/Promise.ts index e6521cf41..7d082cc2d 100644 --- a/ts/session/utils/Promise.ts +++ b/ts/session/utils/Promise.ts @@ -17,11 +17,11 @@ export class TaskTimedOutError extends Error { // one action resolves all const oneAtaTimeRecord: Record> = {}; -export async function allowOnlyOneAtATime( +export async function allowOnlyOneAtATime( name: string, - process: () => Promise, + process: () => Promise, timeoutMs?: number -) { +): Promise { // if currently not in progress if (oneAtaTimeRecord[name] === undefined) { // set lock @@ -37,7 +37,7 @@ export async function allowOnlyOneAtATime( }, timeoutMs); } // do actual work - let innerRetVal; + let innerRetVal: T | undefined; try { innerRetVal = await process(); } catch (e) { From 256672e5a32799d368b9218e8cdc32857281d37f Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Oct 2022 10:11:38 +1100 Subject: [PATCH 14/17] test: fix theme switch test --- ts/test/automation/switching_theme.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ts/test/automation/switching_theme.spec.ts b/ts/test/automation/switching_theme.spec.ts index 26f0a7025..cfb0e3647 100644 --- a/ts/test/automation/switching_theme.spec.ts +++ b/ts/test/automation/switching_theme.spec.ts @@ -15,15 +15,17 @@ test('Switch themes', async () => { windows = windowLoggedIn.windows; const [windowA] = windows; // Check light theme colour is correct - const lightThemeColor = windowA.locator('.inbox.index'); - await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)'); + const darkThemeColor = windowA.locator('.inbox.index'); + await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)'); + // Click theme button and change to dark theme await clickOnTestIdWithText(windowA, 'theme-section'); // Check background colour of background to verify dark theme - const darkThemeColor = windowA.locator('.inbox.index'); - await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(23, 23, 23)'); + const lightThemeColor = windowA.locator('.inbox.index'); + await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)'); + // Toggle back to light theme await clickOnTestIdWithText(windowA, 'theme-section'); // Check background colour again - await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)'); + await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)'); }); From 69a07a8eb1089732b89e4e41d922cd81dddce4b5 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Oct 2022 10:24:08 +1100 Subject: [PATCH 15/17] test: fix change avatar test --- .../avatar-updated-blue-linux.jpeg | Bin 1244 -> 1113 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ts/test/automation/change_avatar.spec.ts-snapshots/avatar-updated-blue-linux.jpeg b/ts/test/automation/change_avatar.spec.ts-snapshots/avatar-updated-blue-linux.jpeg index ba865f6cf495f81e5c248f21f9e2c9c11972a1f2..c45feae52418c810b83ff25a1448dc738c505d06 100644 GIT binary patch delta 412 zcmcb^d6Q#;9utefMuT}w^#Ker%#2D5OoEKef{g!kf&c>&U}0tE0Lmx|FfaiD zBP&9Rk%^f_P)Lzg$+BIjOyZ2DWzu}{uQ>gze*za`NQ z2i5%j$^v&^`J$g#p0e%YMfVkFSa(VouhR>tx%55iO-ERsyb{yf6TbWhzZoj~Exfdf zC+9)f>SNv4i{4I|oDi|*Sl9i+`j=DYw)1S?xHWsaW*yJxuRGS<$zszh&Dt|lWy}1G zTZYAJ!ctBy-?Gb<{mj}oo@dX_Sox*&-y5mJt4m%UYMv|ZH}g~T-*}#Va&A^%udDo< zcjiFigXCfbb!F|lFRG?5OnZ}d*J{O9u_x(%Cw3ex@W1I8{ES?{#>;?8}6G={s#-dCi+$Im4_>;B@Pjnv&}CfO8LI=2d@U_FKOSa{&!UZOZee0Q|om%je;r3JIZ+Dm- z=ChQ4bmV%$v*S$tnVu(=zKYLe{Jm~Rl}+Veq#-}IP|V=)t16*1m)F)BnHMjaf8nrN zy5--JkS!baSE^PEzP|V~cskECbCFAT7*!a|{11p+{U9gsbh>a+?9FvE!$nmu+-lz? z!2Wpu^OVyPsC3E From ec5f3307acb5a7a149e13264236db9bcd9869ccd Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Oct 2022 17:01:37 +1100 Subject: [PATCH 16/17] test: fix integration tests --- .../dialog/SessionPasswordDialog.tsx | 40 ++++++------ .../settings/section/CategoryPrivacy.tsx | 2 + .../automation/disappearing_messages.spec.ts | 5 +- ts/test/automation/password.spec.ts | 63 ++++++++++--------- ts/test/automation/utils.ts | 11 ++-- 5 files changed, 70 insertions(+), 51 deletions(-) diff --git a/ts/components/dialog/SessionPasswordDialog.tsx b/ts/components/dialog/SessionPasswordDialog.tsx index 840a90c02..e085431fe 100644 --- a/ts/components/dialog/SessionPasswordDialog.tsx +++ b/ts/components/dialog/SessionPasswordDialog.tsx @@ -42,12 +42,18 @@ export class SessionPasswordDialog extends React.Component { } public componentDidMount() { + document.addEventListener('keyup', this.onEnterPressed); + setTimeout(() => { // tslint:disable-next-line: no-unused-expression this.passportInput && this.passportInput.focus(); }, 1); } + public componentWillUnmount() { + document.removeEventListener('keyup', this.onEnterPressed); + } + public render() { const { passwordAction } = this.props; let placeholders: Array = []; @@ -93,7 +99,8 @@ export class SessionPasswordDialog extends React.Component { this.passportInput = input; }} placeholder={placeholders[0]} - onKeyUp={this.onPasswordInput} + onChange={this.onPasswordInput} + onPaste={this.onPasswordInput} data-testid="password-input" /> {passwordAction !== 'enter' && passwordAction !== 'remove' && ( @@ -101,7 +108,8 @@ export class SessionPasswordDialog extends React.Component { type="password" id="password-modal-input-confirm" placeholder={placeholders[1]} - onKeyUp={this.onPasswordConfirmInput} + onChange={this.onPasswordConfirmInput} + onPaste={this.onPasswordConfirmInput} data-testid="password-input-confirm" /> )} @@ -110,7 +118,8 @@ export class SessionPasswordDialog extends React.Component { type="password" id="password-modal-input-reconfirm" placeholder={placeholders[2]} - onKeyUp={this.onPasswordRetypeInput} + onPaste={this.onPasswordRetypeInput} + onChange={this.onPasswordRetypeInput} data-testid="password-input-reconfirm" /> )} @@ -258,6 +267,13 @@ export class SessionPasswordDialog extends React.Component { this.closeDialog(); } + private async onEnterPressed(event: any) { + if (event.key === 'Enter') { + event.stopPropagation(); + return this.setPassword(); + } + } + private async handleActionEnter(enteredPassword: string) { // be sure the password is valid if (!this.validatePassword(enteredPassword)) { @@ -321,30 +337,18 @@ export class SessionPasswordDialog extends React.Component { window.inboxStore?.dispatch(sessionPassword(null)); } - private async onPasswordInput(event: any) { - if (event.key === 'Enter') { - return this.setPassword(); - } + private onPasswordInput(event: any) { const currentPasswordEntered = event.target.value; - this.setState({ currentPasswordEntered }); } - private async onPasswordConfirmInput(event: any) { - if (event.key === 'Enter') { - return this.setPassword(); - } + private onPasswordConfirmInput(event: any) { const currentPasswordConfirmEntered = event.target.value; - this.setState({ currentPasswordConfirmEntered }); } - private async onPasswordRetypeInput(event: any) { - if (event.key === 'Enter') { - return this.setPassword(); - } + private onPasswordRetypeInput(event: any) { const currentPasswordRetypeEntered = event.target.value; - this.setState({ currentPasswordRetypeEntered }); } } diff --git a/ts/components/settings/section/CategoryPrivacy.tsx b/ts/components/settings/section/CategoryPrivacy.tsx index f60cb2754..0e8d14b5a 100644 --- a/ts/components/settings/section/CategoryPrivacy.tsx +++ b/ts/components/settings/section/CategoryPrivacy.tsx @@ -97,6 +97,7 @@ export const SettingsCategoryPrivacy = (props: { displayPasswordModal('change', props.onPasswordUpdated); }} buttonText={window.i18n('changePassword')} + dataTestId="change-password-settings-button" /> )} {props.hasPassword && ( @@ -108,6 +109,7 @@ export const SettingsCategoryPrivacy = (props: { }} buttonColor={SessionButtonColor.Danger} buttonText={window.i18n('removePassword')} + dataTestId="remove-password-settings-button" /> )} diff --git a/ts/test/automation/disappearing_messages.spec.ts b/ts/test/automation/disappearing_messages.spec.ts index 5a2b554ec..38fa84cdb 100644 --- a/ts/test/automation/disappearing_messages.spec.ts +++ b/ts/test/automation/disappearing_messages.spec.ts @@ -10,6 +10,7 @@ import { waitForReadableMessageWithText, waitForTestIdWithText, } from './utils'; +import { sleepFor } from '../../session/utils/Promise'; let windows: Array = []; test.beforeEach(beforeAllClean); @@ -50,7 +51,9 @@ test('Disappearing Messages', async () => { 'readable-message', 'You set the disappearing message timer to 5 seconds' ); + await sleepFor(2000); // Check top right hand corner indicator + await waitForTestIdWithText(windowA, 'disappearing-messages-indicator', '5 seconds'); // Send message // Wait for tick of confirmation @@ -87,7 +90,7 @@ test('Disappearing Messages', async () => { `${userA.userName} set the disappearing message timer to 5 seconds` ); // Wait 5 seconds - await waitForMatchingText(windowB, `${userA.userName} disabled disappearing messages`); + await waitForMatchingText(windowB, `${userA.userName} has turned off disappearing messages.`); // verify message is deleted in windowB const errorDesc2 = 'Should not be found'; try { diff --git a/ts/test/automation/password.spec.ts b/ts/test/automation/password.spec.ts index 15475b58c..17c9cf177 100644 --- a/ts/test/automation/password.spec.ts +++ b/ts/test/automation/password.spec.ts @@ -1,4 +1,5 @@ import { _electron, Page, test } from '@playwright/test'; +import { sleepFor } from '../../session/utils/Promise'; import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; import { newUser } from './setup/new_user'; import { openAppAndWait } from './setup/open'; @@ -36,42 +37,40 @@ test.describe('Password checks', () => { await clickOnTestIdWithText(window, 'set-password-button'); // Enter password await typeIntoInput(window, 'password-input', testPassword); - await window.keyboard.press('Delete'); // Confirm password await typeIntoInput(window, 'password-input-confirm', testPassword); - await window.keyboard.press('Delete'); - // Click OK - await clickOnMatchingText(window, 'OK'); - // await window.keyboard.press('Enter'); + // Click Done + await clickOnMatchingText(window, 'Done'); // Check toast notification await waitForTestIdWithText( window, 'session-toast', - 'Your password has been set. Please keep it safe' + 'Your password has been set. Please keep it safe.' ); + // Click on settings tab + await sleepFor(300); + await clickOnTestIdWithText(window, 'settings-section'); // Type password into input field await typeIntoInput(window, 'password-input', testPassword); - // Click OK - await clickOnMatchingText(window, 'OK'); + + // Click Done + await clickOnMatchingText(window, 'Done'); + await clickOnTestIdWithText(window, 'settings-section'); + // Change password - await clickOnMatchingText(window, 'Change Password'); + await clickOnTestIdWithText(window, 'change-password-settings-button', 'Change Password'); + + console.warn('clicked Change Password'); // Enter old password await typeIntoInput(window, 'password-input', testPassword); - await window.keyboard.press('Delete'); // Enter new password await typeIntoInput(window, 'password-input-confirm', newTestPassword); - await window.keyboard.press('Delete'); - // await window.fill('#password-modal-input-confirm', newTestPassword); await window.keyboard.press('Tab'); // Confirm new password await typeIntoInput(window, 'password-input-reconfirm', newTestPassword); - await window.keyboard.press('Delete'); - // await window.fill('#password-modal-input-reconfirm', newTestPassword); // Press enter on keyboard await window.keyboard.press('Enter'); - // Select OK - await clickOnMatchingText(window, 'OK'); // Check toast notification for 'changed password' await waitForTestIdWithText( window, @@ -92,36 +91,44 @@ test.describe('Password checks', () => { await clickOnMatchingText(window, 'Set Password'); // Enter password await typeIntoInput(window, 'password-input', testPassword); - await window.keyboard.press('Delete'); // Confirm password await typeIntoInput(window, 'password-input-confirm', testPassword); - await window.keyboard.press('Delete'); - // Click OK + // Click Done await window.keyboard.press('Enter'); + // // Click on settings tab + await sleepFor(100); + await clickOnTestIdWithText(window, 'settings-section'); + // Type password into input field + await sleepFor(100); await typeIntoInput(window, 'password-input', testPassword); - await window.keyboard.press('Delete'); - // Click OK - await clickOnMatchingText(window, 'OK'); - // Navigate away from settings tab + // Click Done + await clickOnMatchingText(window, 'Done'); + await sleepFor(100); + await window.mouse.click(0, 0); await clickOnTestIdWithText(window, 'message-section'); + await sleepFor(100); + // // Click on settings tab + await sleepFor(1000); await clickOnTestIdWithText(window, 'settings-section'); // // Try with incorrect password - await typeIntoInput(window, 'password-input', '0000'); - await window.keyboard.press('Delete'); + await typeIntoInput(window, 'password-input', '000000'); // Confirm - await clickOnMatchingText(window, 'OK'); + await clickOnMatchingText(window, 'Done'); // // invalid password banner showing? await waitForMatchingText(window, 'Invalid password'); // // Empty password // // Navigate away from settings tab + await window.mouse.click(0, 0); + await sleepFor(100); await clickOnTestIdWithText(window, 'message-section'); + await sleepFor(100); // // Click on settings tab await clickOnTestIdWithText(window, 'settings-section'); // // No password entered - await clickOnMatchingText(window, 'OK'); + await clickOnMatchingText(window, 'Done'); // // Banner should ask for password to be entered - await waitForMatchingText(window, 'Please enter your password'); + await waitForMatchingText(window, 'Enter password'); }); }); diff --git a/ts/test/automation/utils.ts b/ts/test/automation/utils.ts index 2ca330ba0..8f3fe24c5 100644 --- a/ts/test/automation/utils.ts +++ b/ts/test/automation/utils.ts @@ -28,15 +28,17 @@ export async function waitForMatchingText(window: Page, text: string) { } export async function clickOnMatchingText(window: Page, text: string, rightButton = false) { + console.info(`clickOnMatchingText: "${text}"`); return window.click(`"${text}"`, rightButton ? { button: 'right' } : undefined); } export async function clickOnTestIdWithText(window: Page, dataTestId: string, text?: string) { - if (text) { - return window.click(`css=[data-testid=${dataTestId}]:has-text("${text}")`); - } + console.info(`clickOnTestIdWithText with testId:${dataTestId} and text:${text ? text : 'none'}`); + + const builtSelector = !text + ? `css=[data-testid=${dataTestId}]` + : `css=[data-testid=${dataTestId}]:has-text("${text}")`; - const builtSelector = `css=[data-testid=${dataTestId}]`; await window.waitForSelector(builtSelector); return window.click(builtSelector); } @@ -46,6 +48,7 @@ export function getMessageTextContentNow() { } export async function typeIntoInput(window: Page, dataTestId: string, text: string) { + console.info(`typeIntoInput testId: ${dataTestId} : "${text}"`); const builtSelector = `css=[data-testid=${dataTestId}]`; return window.fill(builtSelector, text); } From 8f426a5b53d01a2338837d07586ebdaec6a8530e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 27 Oct 2022 14:53:58 +1100 Subject: [PATCH 17/17] chore: bump to Session v1.10.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 67d1d3005..cccad15f6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "session-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "version": "1.10.3", + "version": "1.10.4", "license": "GPL-3.0", "author": { "name": "Oxen Labs",