From 9557fdd69ca5fc310bce27d3991571b8e0bf92ff Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 19 Oct 2022 11:40:34 -0300 Subject: [PATCH 1/9] 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 1af816b57b..67d1d30052 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 2/9] 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 580586cfcf..b4102bf231 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 3/9] 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 b4102bf231..99da0b924e 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 12f0ed8b4e..e71510f3a5 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 62510df093..563db45f86 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 4/9] 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 b7f4ae1d81..7e790e78f1 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 3d0703fb8e..b5a5617305 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 af06579865..59a452bbea 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 f7a471923f..e0aab38cb5 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 f5fe4759c9..a678bb4fd3 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 b7ec3c348c..170dfa2138 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 c2255655ff..a673a3c4ab 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 5/9] 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 1453390793..27567233c2 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 c6a6e4ac5b..07cea658ac 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 b5a5617305..488ad5d617 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 91fb756285..12231421ee 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 e6521cf410..7d082cc2db 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 6/9] 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 26f0a70255..cfb0e36473 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 7/9] 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 8/9] 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 840a90c021..e085431fee 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 f60cb2754f..0e8d14b5a1 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 5a2b554ec9..38fa84cdbe 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 15475b58cd..17c9cf1772 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 2ca330ba04..8f3fe24c5f 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 9/9] 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 67d1d30052..cccad15f6c 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",