Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Improve UX around decryption errors #8272

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
71ea1bd
Improve UX around decryption errors
duxovni Apr 8, 2022
f3ccd99
Merge branch 'develop' into fayed/uisi-history-display
duxovni Apr 19, 2022
c2f90ea
Don't use msgtype to check for decryption failures
duxovni Apr 19, 2022
75e0ce9
use CSS variables for lengths where possible/appropriate
duxovni Apr 19, 2022
2d4969b
Change DecryptionFailureBody to use text, in a color that contrasts w…
duxovni Apr 25, 2022
4febeef
Merge branch 'develop' into fayed/uisi-history-display
duxovni May 30, 2022
58b4cfd
Redo decryption failure bar with headline/body layout
duxovni Jun 3, 2022
f9ada01
Merge branch 'develop' into fayed/uisi-history-display
duxovni Jun 3, 2022
e37cde2
lint and ts fixes
duxovni Jun 3, 2022
536b563
Merge branch 'develop' into fayed/uisi-history-display
duxovni Jun 7, 2022
a6fe8c1
misc fixes for DecryptionFailureBar
duxovni Jun 7, 2022
0a1eadc
Add loading spinner for the first 5 seconds
duxovni Jun 7, 2022
0b60a61
fix CSS alignments
duxovni Jun 8, 2022
4c1cef0
Merge branch 'develop' into fayed/uisi-history-display
duxovni Jun 8, 2022
704838f
simplify css
duxovni Jun 9, 2022
935aa61
Merge branch 'develop' into fayed/uisi-history-display
t3chguy Jun 9, 2022
4e6a1c6
Apply suggestions from code review
duxovni Jun 9, 2022
f040405
fix copyright year
duxovni Jun 9, 2022
09328e0
we're tracking rendered decryption failures, not visible decryption f…
duxovni Jun 13, 2022
fdb25b5
Merge branch 'develop' into fayed/uisi-history-display
duxovni Jun 13, 2022
a64f47d
more visible/rendered changes
duxovni Jun 13, 2022
7dcfc0f
Cypress tests for DecryptionFailureBar
duxovni Jun 27, 2022
97ea150
Merge branch 'develop' into fayed/uisi-history-display
duxovni Jun 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cypress/integration/10-user-view/user-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ describe("UserView", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Violet");
cy.getBot(synapse, "Usman").as("bot");
cy.initElementWithNewUser(synapse, "Violet");
cy.registerBot(synapse, "Usman").as("bot");
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ describe("Room Directory", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Ray");
cy.getBot(synapse, "Paul").as("bot");
cy.initElementWithNewUser(synapse, "Ray");
cy.registerBot(synapse, "Paul").as("bot");
});
});

Expand Down
6 changes: 3 additions & 3 deletions cypress/integration/12-spotlight/spotlight.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@ describe("Spotlight", () => {
beforeEach(() => {
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, "Jim").then(() =>
cy.getBot(synapse, bot1Name).then(_bot1 => {
cy.initElementWithNewUser(synapse, "Jim").then(() =>
cy.registerBot(synapse, bot1Name).then(_bot1 => {
bot1 = _bot1;
}),
).then(() =>
cy.getBot(synapse, bot2Name).then(_bot2 => {
cy.registerBot(synapse, bot2Name).then(_bot2 => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bot2 = _bot2;
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("Pills", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Sally");
cy.initElementWithNewUser(synapse, "Sally");
});
});

Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/2-login/consent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("Consent", () => {
cy.startSynapse("consent").then(data => {
synapse = data;

cy.initTestUser(synapse, "Bob");
cy.initElementWithNewUser(synapse, "Bob");
});
});

Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/2-login/login.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe("Login", () => {

describe("logout", () => {
beforeEach(() => {
cy.initTestUser(synapse, "Erin");
cy.initElementWithNewUser(synapse, "Erin");
});

it("should go to login page on logout", () => {
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/3-user-menu/user-menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("User Menu", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Jeff").then(credentials => {
cy.initElementWithNewUser(synapse, "Jeff").then(credentials => {
user = credentials;
});
});
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/4-create-room/create-room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe("Create Room", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Jim");
cy.initElementWithNewUser(synapse, "Jim");
});
});

Expand Down
4 changes: 2 additions & 2 deletions cypress/integration/5-threads/threads.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe("Threads", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Tom");
cy.initElementWithNewUser(synapse, "Tom");
});
});

Expand Down Expand Up @@ -73,7 +73,7 @@ describe("Threads", () => {

it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(synapse, "BotBob").then(_bot => {
cy.registerBot(synapse, "BotBob").then(_bot => {
bot = _bot;
});

Expand Down
6 changes: 3 additions & 3 deletions cypress/integration/6-spaces/spaces.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe("Spaces", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Sue").then(_user => {
cy.initElementWithNewUser(synapse, "Sue").then(_user => {
user = _user;
cy.mockClipboard();
});
Expand Down Expand Up @@ -167,7 +167,7 @@ describe("Spaces", () => {

it("should allow user to invite another to a space", () => {
let bot: MatrixClient;
cy.getBot(synapse, "BotBob").then(_bot => {
cy.registerBot(synapse, "BotBob").then(_bot => {
bot = _bot;
});

Expand Down Expand Up @@ -202,7 +202,7 @@ describe("Spaces", () => {
});
getSpacePanelButton("My Space").should("exist");

cy.getBot(synapse, "BotBob").then({ timeout: 10000 }, async bot => {
cy.registerBot(synapse, "BotBob").then({ timeout: 10000 }, async bot => {
const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space"));
await bot.invite(roomId, user.userId);
});
Expand Down
82 changes: 74 additions & 8 deletions cypress/integration/7-crypto/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,30 @@ limitations under the License.

import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { UserCredentials } from "../../support/login";

function waitForEncryption(cli: MatrixClient, roomId: string, win: Cypress.AUTWindow): Promise<void> {
return new Promise<void>(resolve => {
const onEvent = () => {
cli.crypto.cryptoStore.getEndToEndRooms(null, (result) => {
if (result[roomId]) {
cli.off(win.matrixcs.ClientEvent.Event, onEvent);
resolve();
}
});
try {
cli.crypto.cryptoStore.getEndToEndRooms(null, (result) => {
if (result[roomId]) {
cli.off(win.matrixcs.ClientEvent.Event, onEvent);
resolve();
}
});
} catch {
}
};
cli.on(win.matrixcs.ClientEvent.Event, onEvent);
onEvent();
});
}

describe("Cryptography", () => {
beforeEach(() => {
cy.startSynapse("default").as('synapse').then(
synapse => cy.initTestUser(synapse, "Alice"),
synapse => cy.initElementWithNewUser(synapse, "Alice").as("aliceCredentials"),
);
});

Expand All @@ -45,7 +50,7 @@ describe("Cryptography", () => {
});

it("should receive and decrypt encrypted messages", () => {
cy.get<SynapseInstance>('@synapse').then(synapse => cy.getBot(synapse, "Beatrice").as('bot'));
cy.get<SynapseInstance>('@synapse').then(synapse => cy.registerBot(synapse, "Beatrice").as('bot'));

cy.createRoom({
initial_state: [
Expand Down Expand Up @@ -83,4 +88,65 @@ describe("Cryptography", () => {
.closest(".mx_EventTile_line")
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
});

it("should display a banner when messages fail to decrypt", () => {
cy.createRoom({
initial_state: [
{
type: "m.room.encryption",
state_key: '',
content: {
algorithm: "m.megolm.v1.aes-sha2",
},
},
],
}).as('roomId');

cy.all([
cy.getClient(),
cy.get<string>('@roomId'),
]).then(([cli, roomId]) => {
cy.visit("/#/room/" + roomId);
cy.get(".mx_cryptoEvent").should("contain", "Encryption enabled").then(() =>
cy.wrap(
cli.sendMessage(roomId, {
body: "Top secret message",
msgtype: "m.text",
}),
),
);
});

cy.all([
cy.get<SynapseInstance>('@synapse'),
cy.get<UserCredentials>('@aliceCredentials'),
]).then(([synapse, credentials]) => {
cy.window().then(win => { win.location.href = 'about:blank'; });
cy.initElementWithExistingUser(synapse, credentials.userId, credentials.password, true);
});

cy.get<string>('@roomId').then(roomId => cy.visit("/#/room/" + roomId));

cy.get(".mx_DecryptionFailureBar_message_headline").should("contain", "Decrypting messages...");
cy.get(".mx_DecryptionFailureBar .mx_Spinner");

// Spinner times out after 5 seconds
cy.get(".mx_DecryptionFailureBar_message_headline", { timeout: 6000 })
.should("contain", "Requesting keys to decrypt messages...");
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_icon");

// Decryption failure bar is shown/hidden as messages are scrolled in/out of the DOM
cy.all([
cy.getClient(),
cy.get<string>('@roomId'),
]).then(([cli, roomId]) =>
cy.wrap(cli.sendMessage(roomId, {
body: "test\n".repeat(500),
msgtype: "m.text",
})),
);
cy.get(".mx_DecryptionFailureBar").should("not.exist");
cy.get(".mx_ScrollPanel").scrollTo("top");
cy.get(".mx_DecryptionFailureBar");
});
});
2 changes: 1 addition & 1 deletion cypress/integration/8-update/update.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("Update", () => {
},
}).as("version");

cy.initTestUser(synapse, "Ursa");
cy.initElementWithNewUser(synapse, "Ursa");

cy.wait("@version");
cy.url().should("contain", "updated=" + NEW_VERSION).then(href => {
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/9-widgets/stickers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe("Stickers", () => {
cy.startSynapse("default").then(data => {
synapse = data;

cy.initTestUser(synapse, "Sally");
cy.initElementWithNewUser(synapse, "Sally");
});
cy.serveHtmlFile(WIDGET_HTML).then(url => {
stickerPickerUrl = url;
Expand Down
79 changes: 50 additions & 29 deletions cypress/support/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,50 +20,71 @@ import request from "browser-request";

import type { MatrixClient } from "matrix-js-sdk/src/client";
import { SynapseInstance } from "../plugins/synapsedocker";
import { UserCredentials } from "./login";
import Chainable = Cypress.Chainable;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Returns a new Bot instance
* Returns a Bot instance for a newly-registered account
* @param synapse the instance on which to register the bot user
* @param displayName the display name to give to the bot user
*/
getBot(synapse: SynapseInstance, displayName?: string): Chainable<MatrixClient>;
registerBot(synapse: SynapseInstance, displayName?: string): Chainable<MatrixClient>;

/**
* Returns a Bot instance for an existing user account
* @param synapse the instance on which to register the bot user
* @param username the account's username
* @param password the account's password
*/
loginBot(synapse: SynapseInstance, username: string, password: string): Chainable<MatrixClient>;
}
}
}

Cypress.Commands.add("getBot", (synapse: SynapseInstance, displayName?: string): Chainable<MatrixClient> => {
const username = Cypress._.uniqueId("userId_");
const password = Cypress._.uniqueId("password_");
return cy.registerUser(synapse, username, password, displayName).then(credentials => {
return cy.window({ log: false }).then(win => {
const cli = new win.matrixcs.MatrixClient({
baseUrl: synapse.baseUrl,
userId: credentials.userId,
deviceId: credentials.deviceId,
accessToken: credentials.accessToken,
request,
store: new win.matrixcs.MemoryStore(),
scheduler: new win.matrixcs.MatrixScheduler(),
cryptoStore: new win.matrixcs.MemoryCryptoStore(),
});

cli.on(win.matrixcs.RoomMemberEvent.Membership, (event, member) => {
if (member.membership === "invite" && member.userId === cli.getUserId()) {
cli.joinRoom(member.roomId);
}
});
function getBot(synapse: SynapseInstance, credentials: UserCredentials): Chainable<MatrixClient> {
return cy.window({ log: false }).then(win => {
const cli = new win.matrixcs.MatrixClient({
baseUrl: synapse.baseUrl,
userId: credentials.userId,
deviceId: credentials.deviceId,
accessToken: credentials.accessToken,
request,
store: new win.matrixcs.MemoryStore(),
scheduler: new win.matrixcs.MatrixScheduler(),
cryptoStore: new win.matrixcs.MemoryCryptoStore(),
});

return cy.wrap(
cli.initCrypto()
.then(() => cli.setGlobalErrorOnUnknownDevices(false))
.then(() => cli.startClient())
.then(() => cli),
);
cli.on(win.matrixcs.RoomMemberEvent.Membership, (event, member) => {
if (member.membership === "invite" && member.userId === cli.getUserId()) {
cli.joinRoom(member.roomId);
}
});

return cy.wrap(
cli.initCrypto()
.then(() => cli.setGlobalErrorOnUnknownDevices(false))
.then(() => cli.startClient())
.then(() => cli),
);
});
}

Cypress.Commands.add("registerBot", (synapse: SynapseInstance, displayName?: string): Chainable<MatrixClient> => {
const username = Cypress._.uniqueId("userId_");
const password = Cypress._.uniqueId("password_");
return cy.registerUser(synapse, username, password, displayName)
.then(credentials => getBot(synapse, { ...credentials, password }));
});

Cypress.Commands.add("loginBot", (
synapse: SynapseInstance,
username: string,
password: string,
): Chainable<MatrixClient> => {
return cy.loginUser(synapse, username, password)
.then(credentials => getBot(synapse, { ...credentials, password }));
});
Loading