Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit 993a702

Browse files
authored
Show a progress bar while migrating from legacy crypto (matrix-org#12104)
* Show a progress bar during migration of crypto data * playwright: add new `pageWithCredentials` fixture * Add a playwright test for migration progress * Add documentation for `idbSave`
1 parent 2d3351b commit 993a702

File tree

12 files changed

+72194
-5
lines changed

12 files changed

+72194
-5
lines changed

.prettierignore

+3
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ yarn.lock
1717

1818
# This file is owned, parsed, and generated by allchange, which doesn't comply with prettier
1919
/CHANGELOG.md
20+
21+
# This file is also machine-generated
22+
/playwright/e2e/crypto/test_indexeddb_cryptostore_dump/dump.json
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright 2023-2024 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import path from "path";
18+
import { readFile } from "node:fs/promises";
19+
20+
import { expect, test as base } from "../../element-web-test";
21+
22+
const test = base.extend({
23+
// Replace the `user` fixture with one which populates the indexeddb data before starting the app.
24+
user: async ({ context, pageWithCredentials: page, credentials }, use) => {
25+
await page.route(`/test_indexeddb_cryptostore_dump/*`, async (route, request) => {
26+
const resourcePath = path.join(__dirname, new URL(request.url()).pathname);
27+
const body = await readFile(resourcePath, { encoding: "utf-8" });
28+
await route.fulfill({ body });
29+
});
30+
await page.goto("/test_indexeddb_cryptostore_dump/index.html");
31+
32+
await use(credentials);
33+
},
34+
});
35+
36+
test.describe("migration", function () {
37+
test.use({ displayName: "Alice" });
38+
39+
test("Should support migration from legacy crypto", async ({ context, user, page }, workerInfo) => {
40+
test.skip(workerInfo.project.name === "Legacy Crypto", "This test only works with Rust crypto.");
41+
test.slow();
42+
43+
// We should see a migration progress bar
44+
await page.getByText("Hang tight.").waitFor({ timeout: 60000 });
45+
46+
// When the progress bar first loads, it should have a high max (one per megolm session to import), and
47+
// a relatively low value.
48+
const progressBar = page.getByRole("progressbar");
49+
const initialProgress = parseFloat(await progressBar.getAttribute("value"));
50+
const initialMax = parseFloat(await progressBar.getAttribute("max"));
51+
expect(initialMax).toBeGreaterThan(4000);
52+
expect(initialProgress).toBeGreaterThanOrEqual(0);
53+
expect(initialProgress).toBeLessThanOrEqual(500);
54+
55+
// Later, the progress should pass 50%
56+
await expect
57+
.poll(
58+
async () => {
59+
const progressBar = page.getByRole("progressbar");
60+
return (
61+
(parseFloat(await progressBar.getAttribute("value")) * 100.0) /
62+
parseFloat(await progressBar.getAttribute("max"))
63+
);
64+
},
65+
{ timeout: 60000 },
66+
)
67+
.toBeGreaterThan(50);
68+
69+
// Eventually, we should get a normal matrix chat
70+
await page.waitForSelector(".mx_MatrixChat", { timeout: 120000 });
71+
});
72+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Dump of libolm indexeddb cryptostore
2+
3+
This directory contains, in `dump.json`, a dump of a real indexeddb store from a session using
4+
libolm crypto.
5+
6+
The corresponding pickle key is `+1k2Ppd7HIisUY824v7JtV3/oEE4yX0TqtmNPyhaD7o`.
7+
8+
This directory also contains, in `index.html` and `load.js`, a page which will populate indexeddb with the data
9+
(and the pickle key). This can be served via a Playwright [Route](https://playwright.dev/docs/api/class-route) so as to
10+
populate the indexeddb before the main application loads. Note that encrypting the pickle key requires the test User ID
11+
and Device ID, so they must be stored in `localstorage` before loading `index.html`.
12+
13+
## Creation of the dump file
14+
15+
The dump was created by pasting the following into the browser console:
16+
17+
```javascript
18+
async function exportIndexedDb(name) {
19+
const db = await new Promise((resolve, reject) => {
20+
const dbReq = indexedDB.open(name);
21+
dbReq.onerror = reject;
22+
dbReq.onsuccess = () => resolve(dbReq.result);
23+
});
24+
25+
const storeNames = db.objectStoreNames;
26+
const exports = {};
27+
for (const store of storeNames) {
28+
exports[store] = [];
29+
const txn = db.transaction(store, "readonly");
30+
const objectStore = txn.objectStore(store);
31+
await new Promise((resolve, reject) => {
32+
const cursorReq = objectStore.openCursor();
33+
cursorReq.onerror = reject;
34+
cursorReq.onsuccess = (event) => {
35+
const cursor = event.target.result;
36+
if (cursor) {
37+
const entry = { value: cursor.value };
38+
if (!objectStore.keyPath) {
39+
entry.key = cursor.key;
40+
}
41+
exports[store].push(entry);
42+
cursor.continue();
43+
} else {
44+
resolve();
45+
}
46+
};
47+
});
48+
}
49+
return exports;
50+
}
51+
52+
window.saveAs(
53+
new Blob([JSON.stringify(await exportIndexedDb("matrix-js-sdk:crypto"), null, 2)], {
54+
type: "application/json;charset=utf-8",
55+
}),
56+
"dump.json",
57+
);
58+
```
59+
60+
The pickle key is extracted via `mxMatrixClientPeg.get().crypto.olmDevice.pickleKey`.

0 commit comments

Comments
 (0)