mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-18 13:40:55 -04:00
Merge branch 'main' into test-sliding-sync-timeline-limit-duplication
This commit is contained in:
28
.github/workflows/bindings_ci.yml
vendored
28
.github/workflows/bindings_ci.yml
vendored
@@ -75,6 +75,34 @@ jobs:
|
||||
- name: Build library & generate bindings
|
||||
run: target/debug/xtask ci bindings
|
||||
|
||||
lint-js-bindings:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- name: "[m]-crypto-nodejs"
|
||||
path: "bindings/matrix-sdk-crypto-nodejs"
|
||||
- name: "[m]-crypto-js"
|
||||
path: "bindings/matrix-sdk-crypto-js"
|
||||
|
||||
name: lint ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ matrix.path }}
|
||||
run: npm install
|
||||
|
||||
- name: run lint
|
||||
working-directory: ${{ matrix.path }}
|
||||
run: npm run lint
|
||||
|
||||
test-matrix-sdk-crypto-nodejs:
|
||||
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
55
.github/workflows/ci.yml
vendored
55
.github/workflows/ci.yml
vendored
@@ -412,14 +412,13 @@ jobs:
|
||||
|
||||
sliding-sync-integration-tests:
|
||||
name: Sliding Sync Integration test
|
||||
# disabled until we can figure out the weird docker-not-starting-situation
|
||||
if: false
|
||||
# if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
if: github.event_name == 'push' || !github.event.pull_request.draft
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
# Service containers to run with `runner-job`
|
||||
# run several docker containers with the same networking stack so the hostname 'postgres'
|
||||
# maps to the postgres container, etc.
|
||||
services:
|
||||
# Label used to access the service container
|
||||
# sliding sync needs a postgres container
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres
|
||||
@@ -437,7 +436,28 @@ jobs:
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
|
||||
# run sliding sync and point it at the postgres container and synapse container.
|
||||
# the postgres container needs to be above this to make sure it has started prior to this service.
|
||||
slidingsync:
|
||||
image: "ghcr.io/matrix-org/sliding-sync:v0.99.0"
|
||||
env:
|
||||
SYNCV3_SERVER: "http://synapse:8008"
|
||||
SYNCV3_SECRET: "SUPER_CI_SECRET"
|
||||
SYNCV3_BINDADDR: ":8118"
|
||||
SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres"
|
||||
ports:
|
||||
- 8118:8118
|
||||
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
|
||||
# latter does not provide networking for services to communicate with it.
|
||||
synapse:
|
||||
# Custom image built from https://github.com/matrix-org/synapse/tree/v1.72.0/docker/complement
|
||||
# with a dummy /complement/ca set
|
||||
image: ghcr.io/matrix-org/synapse-service:v1.72.0
|
||||
env:
|
||||
SYNAPSE_COMPLEMENT_DATABASE: sqlite
|
||||
SERVER_NAME: synapse
|
||||
ports:
|
||||
- 8008:8008
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v3
|
||||
@@ -455,22 +475,11 @@ jobs:
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
# local synapse
|
||||
- uses: michaelkaye/setup-matrix-synapse@main
|
||||
with:
|
||||
uploadLogs: true
|
||||
httpPort: 8228
|
||||
disableRateLimiting: true
|
||||
|
||||
# latest sliding sync proxy
|
||||
|
||||
- uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
registry: gcr.io
|
||||
image: "matrix-org/sliding-sync:v0.98.0"
|
||||
docker_network: "host"
|
||||
options: '-e "SYNCV3_SERVER=http://locahost:8228" -e "SYNCV3_SECRET=SUPER_CI_SECRET" -e "SYNCV3_BINDADDR=:8118" -e "SYNCV3_DB=user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres" -p 8118:8118'
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
RUST_LOG: "hyper=trace"
|
||||
HOMESERVER_URL: "http://localhost:8008"
|
||||
HOMESERVER_DOMAIN: "synapse"
|
||||
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
|
||||
run: |
|
||||
cargo nextest run -p sliding-sync-integration-tests
|
||||
cargo nextest run -p sliding-sync-integration-test
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2705,6 +2705,7 @@ dependencies = [
|
||||
"matrix-sdk-test",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"ruma",
|
||||
|
||||
1
bindings/matrix-sdk-crypto-js/.prettierignore
Normal file
1
bindings/matrix-sdk-crypto-js/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
/pkg
|
||||
9
bindings/matrix-sdk-crypto-js/.prettierrc.js
Normal file
9
bindings/matrix-sdk-crypto-js/.prettierrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// prettier configuration: the same as the conventions used throughout Matrix.org
|
||||
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
|
||||
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
quoteProps: "consistent",
|
||||
trailingComma: "all",
|
||||
};
|
||||
@@ -49,8 +49,6 @@ $ npm run doc
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[WebAssembly]: https://webassembly.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"fake-indexeddb": "^4.0",
|
||||
"jest": "^28.1.0",
|
||||
"prettier": "^2.8.3",
|
||||
"typedoc": "^0.22.17",
|
||||
"wasm-pack": "^0.10.2",
|
||||
"yargs-parser": "~21.0.1"
|
||||
@@ -38,6 +39,7 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "prettier --check .",
|
||||
"build": "./scripts/build.sh",
|
||||
"test": "jest --verbose",
|
||||
"doc": "typedoc --tsconfig .",
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
// replace 'wasm' with a reference to the exports from the wasm module.
|
||||
//
|
||||
// Ideally this will never get used because the application will call initAsync instead.
|
||||
wasm = new Proxy({}, {
|
||||
get: (target, prop, receiver) => __initSync()[prop],
|
||||
});
|
||||
wasm = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop, receiver) => __initSync()[prop],
|
||||
},
|
||||
);
|
||||
|
||||
let inited = false;
|
||||
__initSync = function() {
|
||||
__initSync = function () {
|
||||
if (inited) {
|
||||
return;
|
||||
}
|
||||
@@ -21,7 +24,7 @@ __initSync = function() {
|
||||
wasm.__wbindgen_start();
|
||||
inited = true;
|
||||
return wasm;
|
||||
}
|
||||
};
|
||||
|
||||
let initPromise = null;
|
||||
|
||||
@@ -39,43 +42,47 @@ module.exports.initAsync = function () {
|
||||
if (!initPromise) {
|
||||
initPromise = Promise.resolve()
|
||||
.then(() => require("./matrix_sdk_crypto_js_bg.wasm.js"))
|
||||
.then(b64 => WebAssembly.instantiate(unbase64(b64), imports))
|
||||
.then(result => {
|
||||
.then((b64) => WebAssembly.instantiate(unbase64(b64), imports))
|
||||
.then((result) => {
|
||||
wasm = result.instance.exports;
|
||||
wasm.__wbindgen_start();
|
||||
inited = true;
|
||||
});
|
||||
}
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
const b64lookup = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]);
|
||||
|
||||
// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8
|
||||
function unbase64(sBase64) {
|
||||
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
|
||||
const nInLen = sB64Enc.length;
|
||||
const nOutLen = (nInLen * 3 + 1) >> 2;
|
||||
const taBytes = new Uint8Array(nOutLen);
|
||||
|
||||
let nMod3;
|
||||
let nMod4;
|
||||
let nUint24 = 0;
|
||||
let nOutIdx = 0;
|
||||
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4));
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
nMod3 = 0;
|
||||
while (nMod3 < 3 && nOutIdx < nOutLen) {
|
||||
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||
nMod3++;
|
||||
nOutIdx++;
|
||||
}
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return taBytes;
|
||||
};
|
||||
|
||||
const b64lookup = new Uint8Array([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32,
|
||||
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
|
||||
]);
|
||||
|
||||
// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8
|
||||
function unbase64(sBase64) {
|
||||
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
|
||||
const nInLen = sB64Enc.length;
|
||||
const nOutLen = (nInLen * 3 + 1) >> 2;
|
||||
const taBytes = new Uint8Array(nOutLen);
|
||||
|
||||
let nMod3;
|
||||
let nMod4;
|
||||
let nUint24 = 0;
|
||||
let nOutIdx = 0;
|
||||
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4));
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
nMod3 = 0;
|
||||
while (nMod3 < 3 && nOutIdx < nOutLen) {
|
||||
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||
nMod3++;
|
||||
nOutIdx++;
|
||||
}
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return taBytes;
|
||||
}
|
||||
|
||||
@@ -145,9 +145,6 @@ mod inner {
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "trace")]
|
||||
fn log_trace(message: String);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console, js_name = "debug")]
|
||||
fn log_debug(message: String);
|
||||
|
||||
@@ -213,7 +210,7 @@ mod inner {
|
||||
let message = format!("{level} {origin}{recorder}");
|
||||
|
||||
match *level {
|
||||
Level::TRACE => log_trace(message),
|
||||
Level::TRACE => log_debug(message),
|
||||
Level::DEBUG => log_debug(message),
|
||||
Level::INFO => log_info(message),
|
||||
Level::WARN => log_warn(message),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const { UserId, initAsync } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
test('can instantiate rust objects with async initialiser', async () => {
|
||||
initUserId = () => new UserId('@foo:bar.org');
|
||||
test("can instantiate rust objects with async initialiser", async () => {
|
||||
initUserId = () => new UserId("@foo:bar.org");
|
||||
|
||||
// stub out the synchronous WebAssembly loader with one that raises an error
|
||||
jest.spyOn(WebAssembly, 'Module').mockImplementation(() => { throw new Error('synchronous WebAssembly.Module() not allowed')});
|
||||
jest.spyOn(WebAssembly, "Module").mockImplementation(() => {
|
||||
throw new Error("synchronous WebAssembly.Module() not allowed");
|
||||
});
|
||||
|
||||
// this should fail
|
||||
expect(initUserId).toThrow(/synchronous/);
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
const { Attachment, EncryptedAttachment } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { Attachment, EncryptedAttachment } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe(Attachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
let encryptedAttachment;
|
||||
|
||||
test('can encrypt data', () => {
|
||||
test("can encrypt data", () => {
|
||||
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
|
||||
|
||||
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
|
||||
|
||||
expect(mediaEncryptionInfo).toMatchObject({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: expect.any(String),
|
||||
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
|
||||
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
|
||||
alg: expect.any(String),
|
||||
k: expect.any(String),
|
||||
ext: expect.any(Boolean),
|
||||
},
|
||||
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
hashes: {
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
|
||||
}
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
},
|
||||
});
|
||||
|
||||
const encryptedData = encryptedAttachment.encryptedData;
|
||||
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
|
||||
expect(
|
||||
encryptedData.every((i) => {
|
||||
i != 0;
|
||||
}),
|
||||
).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can decrypt data', () => {
|
||||
test("can decrypt data", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
|
||||
@@ -40,34 +44,36 @@ describe(Attachment.name, () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can only decrypt once', () => {
|
||||
test("can only decrypt once", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
|
||||
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
|
||||
expect(() => {
|
||||
textDecoder.decode(decryptedAttachment);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptedAttachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
test('can be created manually', () => {
|
||||
test("can be created manually", () => {
|
||||
const encryptedAttachment = new EncryptedAttachment(
|
||||
new Uint8Array([24, 150, 67, 37, 144]),
|
||||
JSON.stringify({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: 'oct',
|
||||
key_ops: [ 'encrypt', 'decrypt' ],
|
||||
alg: 'A256CTR',
|
||||
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
|
||||
ext: true
|
||||
kty: "oct",
|
||||
key_ops: ["encrypt", "decrypt"],
|
||||
alg: "A256CTR",
|
||||
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
|
||||
ext: true,
|
||||
},
|
||||
iv: 'xk2AcWkomiYAAAAAAAAAAA',
|
||||
iv: "xk2AcWkomiYAAAAAAAAAAA",
|
||||
hashes: {
|
||||
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
|
||||
}
|
||||
})
|
||||
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
@@ -27,11 +27,11 @@ const {
|
||||
Qr,
|
||||
QrCode,
|
||||
QrCodeScan,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { zip, addMachineToMachine } = require('./helper');
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
const { zip, addMachineToMachine } = require("./helper");
|
||||
|
||||
describe('LocalTrust', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("LocalTrust", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(LocalTrust.Verified).toStrictEqual(0);
|
||||
expect(LocalTrust.BlackListed).toStrictEqual(1);
|
||||
expect(LocalTrust.Ignored).toStrictEqual(2);
|
||||
@@ -39,8 +39,8 @@ describe('LocalTrust', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeviceKeyName', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("DeviceKeyName", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(DeviceKeyName.Curve25519).toStrictEqual(0);
|
||||
expect(DeviceKeyName.Ed25519).toStrictEqual(1);
|
||||
expect(DeviceKeyName.Unknown).toStrictEqual(2);
|
||||
@@ -48,54 +48,52 @@ describe('DeviceKeyName', () => {
|
||||
});
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("foobar");
|
||||
const room = new RoomId("!baz:matrix.org");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return OlmMachine.initialize(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user devices', async () => {
|
||||
test("can read user devices", async () => {
|
||||
const m = await machine();
|
||||
const userDevices = await m.getUserDevices(user);
|
||||
|
||||
expect(userDevices).toBeInstanceOf(UserDevices);
|
||||
expect(userDevices.get(device)).toBeInstanceOf(Device);
|
||||
expect(userDevices.isAnyVerified()).toStrictEqual(false);
|
||||
expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.keys().map((device_id) => device_id.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.devices().map((device) => device.deviceId.toString())).toStrictEqual([device.toString()]);
|
||||
});
|
||||
|
||||
test('can read a user device', async () => {
|
||||
test("can read a user device", async () => {
|
||||
const m = await machine();
|
||||
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"keys": {
|
||||
JLAFKJWSCS: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "JLAFKJWSCS",
|
||||
keys: {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
|
||||
},
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
unsigned: {
|
||||
device_display_name: "Alice's mobile phone",
|
||||
},
|
||||
"user_id": "@alice:example.org"
|
||||
}
|
||||
}
|
||||
user_id: "@alice:example.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
// Insert another device into the store
|
||||
await m.markRequestAsSent("ID", RequestType.KeysQuery, hypothetical_response);
|
||||
@@ -140,18 +138,18 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Key Verification', () => {
|
||||
const userId1 = new UserId('@alice:example.org');
|
||||
const deviceId1 = new DeviceId('alice_device');
|
||||
describe("Key Verification", () => {
|
||||
const userId1 = new UserId("@alice:example.org");
|
||||
const deviceId1 = new DeviceId("alice_device");
|
||||
|
||||
const userId2 = new UserId('@bob:example.org');
|
||||
const deviceId2 = new DeviceId('bob_device');
|
||||
const userId2 = new UserId("@bob:example.org");
|
||||
const deviceId2 = new DeviceId("bob_device");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return OlmMachine.initialize(new_user || userId1, new_device || deviceId1);
|
||||
}
|
||||
|
||||
describe('SAS', () => {
|
||||
describe("SAS", () => {
|
||||
// First Olm machine.
|
||||
let m1;
|
||||
|
||||
@@ -169,7 +167,7 @@ describe('Key Verification', () => {
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
test("can request verification (`m.key.verification.request`)", async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
@@ -196,7 +194,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
@@ -204,13 +204,17 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -221,7 +225,7 @@ describe('Key Verification', () => {
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
test("can fetch received request verification", async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
@@ -235,7 +239,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
@@ -248,40 +254,52 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request (`m.key.verification.ready`)', async () => {
|
||||
test("can accept a verification request (`m.key.verification.ready`)", async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.accept();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
test("verification requests are synchronized and automatically updated", () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]),
|
||||
);
|
||||
});
|
||||
|
||||
// SAS verification for the second machine.
|
||||
let sas2;
|
||||
|
||||
test('can start a SAS verification (`m.key.verification.start`)', async () => {
|
||||
test("can start a SAS verification (`m.key.verification.start`)", async () => {
|
||||
// Let's start a SAS verification, from `m2` for example.
|
||||
[sas2, outgoingVerificationRequest] = await verificationRequest2.startSas();
|
||||
expect(sas2).toBeInstanceOf(Sas);
|
||||
@@ -308,13 +326,17 @@ describe('Key Verification', () => {
|
||||
expect(sas2.decimals()).toBeUndefined();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the SAS start to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -323,7 +345,7 @@ describe('Key Verification', () => {
|
||||
// SAS verification for the second machine.
|
||||
let sas1;
|
||||
|
||||
test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => {
|
||||
test("can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)", async () => {
|
||||
// Let's fetch the ongoing SAS verification.
|
||||
sas1 = await m1.getVerification(userId2, flowId);
|
||||
|
||||
@@ -353,64 +375,72 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = sas1.accept();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.accept");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the SAS accept to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('emojis are supported by both sides', () => {
|
||||
test("emojis are supported by both sides", () => {
|
||||
expect(sas1.supportsEmoji()).toStrictEqual(true);
|
||||
expect(sas2.supportsEmoji()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('one side sends verification key (`m.key.verification.key`)', async () => {
|
||||
test("one side sends verification key (`m.key.verification.key`)", async () => {
|
||||
// Let's send the verification keys from `m2` to `m1`.
|
||||
const outgoingRequests = await m2.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS key to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m2.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, '{}');
|
||||
m2.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
|
||||
});
|
||||
|
||||
test('other side sends back verification key (`m.key.verification.key`)', async () => {
|
||||
test("other side sends back verification key (`m.key.verification.key`)", async () => {
|
||||
// Let's send the verification keys from `m1` to `m2`.
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.key");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, '{}');
|
||||
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
|
||||
});
|
||||
|
||||
test('emojis match from both sides', () => {
|
||||
test("emojis match from both sides", () => {
|
||||
const emojis1 = sas1.emoji();
|
||||
const emojiIndexes1 = sas1.emojiIndex();
|
||||
const emojis2 = sas2.emoji();
|
||||
@@ -421,9 +451,15 @@ describe('Key Verification', () => {
|
||||
expect(emojis2).toHaveLength(emojis1.length);
|
||||
expect(emojiIndexes2).toHaveLength(emojis1.length);
|
||||
|
||||
const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
|
||||
const isEmoji =
|
||||
/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
|
||||
|
||||
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) {
|
||||
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(
|
||||
emojis1,
|
||||
emojiIndexes1,
|
||||
emojis2,
|
||||
emojiIndexes2,
|
||||
)) {
|
||||
expect(emoji1).toBeInstanceOf(Emoji);
|
||||
expect(emoji1.symbol).toMatch(isEmoji);
|
||||
expect(emoji1.description).toBeTruthy();
|
||||
@@ -439,7 +475,7 @@ describe('Key Verification', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('decimals match from both sides', () => {
|
||||
test("decimals match from both sides", () => {
|
||||
const decimals1 = sas1.decimals();
|
||||
const decimals2 = sas2.decimals();
|
||||
|
||||
@@ -455,7 +491,7 @@ describe('Key Verification', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('can confirm keys match (`m.key.verification.mac`)', async () => {
|
||||
test("can confirm keys match (`m.key.verification.mac`)", async () => {
|
||||
// `m1` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm();
|
||||
|
||||
@@ -465,19 +501,23 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS confirmation to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm back keys match (`m.key.verification.done`)', async () => {
|
||||
test("can confirm back keys match (`m.key.verification.done`)", async () => {
|
||||
// `m2` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm();
|
||||
|
||||
@@ -489,13 +529,17 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.mac");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS confirmation to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -506,41 +550,47 @@ describe('Key Verification', () => {
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[1];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS done to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
}
|
||||
});
|
||||
|
||||
test('can send final done (`m.key.verification.done`)', async () => {
|
||||
test("can send final done (`m.key.verification.done`)", async () => {
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
expect(outgoingRequests).toHaveLength(4);
|
||||
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
expect(toDeviceRequest.event_type).toStrictEqual("m.key.verification.done");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: JSON.parse(toDeviceRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, '{}');
|
||||
m1.markRequestAsSent(toDeviceRequest.id, toDeviceRequest.type, "{}");
|
||||
});
|
||||
|
||||
test('can see if verification is done', () => {
|
||||
test("can see if verification is done", () => {
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(true);
|
||||
|
||||
@@ -549,10 +599,10 @@ describe('Key Verification', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('QR Code', () => {
|
||||
describe("QR Code", () => {
|
||||
if (undefined === Qr) {
|
||||
// qrcode supports is not enabled
|
||||
console.info('qrcode support is disabled, skip the associated test suite');
|
||||
console.info("qrcode support is disabled, skip the associated test suite");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -574,7 +624,7 @@ describe('Key Verification', () => {
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
test("can request verification (`m.key.verification.request`)", async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
@@ -604,7 +654,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
@@ -612,13 +664,17 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.request");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
@@ -629,7 +685,7 @@ describe('Key Verification', () => {
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
test("can fetch received request verification", async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
@@ -643,7 +699,9 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
@@ -656,7 +714,7 @@ describe('Key Verification', () => {
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request with methods (`m.key.verification.ready`)', async () => {
|
||||
test("can accept a verification request with methods (`m.key.verification.ready`)", async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([
|
||||
VerificationMethod.QrCodeScanV1, // by default
|
||||
@@ -666,33 +724,45 @@ describe('Key Verification', () => {
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.ready");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
test("verification requests are synchronized and automatically updated", () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(
|
||||
expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]),
|
||||
);
|
||||
});
|
||||
|
||||
// QR verification for the second machine.
|
||||
let qr2;
|
||||
|
||||
test('can generate a QR code', async () => {
|
||||
test("can generate a QR code", async () => {
|
||||
qr2 = await verificationRequest2.generateQrCode();
|
||||
|
||||
expect(qr2).toBeInstanceOf(Qr);
|
||||
@@ -712,17 +782,19 @@ describe('Key Verification', () => {
|
||||
expect(qr2.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read QR code\'s bytes', async () => {
|
||||
const qrCodeHeader = 'MATRIX';
|
||||
const qrCodeVersion = '\x02';
|
||||
test("can read QR code's bytes", async () => {
|
||||
const qrCodeHeader = "MATRIX";
|
||||
const qrCodeVersion = "\x02";
|
||||
|
||||
const qrCodeBytes = qr2.toBytes();
|
||||
|
||||
expect(qrCodeBytes).toHaveLength(122);
|
||||
expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0)));
|
||||
expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual(
|
||||
[...qrCodeHeader, ...qrCodeVersion].map((char) => char.charCodeAt(0)),
|
||||
);
|
||||
});
|
||||
|
||||
test('can render QR code', async () => {
|
||||
test("can render QR code", async () => {
|
||||
const qrCode = qr2.toQrCode();
|
||||
|
||||
expect(qrCode).toBeInstanceOf(QrCode);
|
||||
@@ -738,7 +810,7 @@ describe('Key Verification', () => {
|
||||
// 45px ⨉ 45px
|
||||
expect(buffer).toHaveLength(45 * 45);
|
||||
// 0 for a white pixel, 1 for a black pixel.
|
||||
expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true);
|
||||
expect(buffer.every((p) => p == 0 || p == 1)).toStrictEqual(true);
|
||||
|
||||
/*
|
||||
const { Canvas } = require('canvas');
|
||||
@@ -795,7 +867,7 @@ describe('Key Verification', () => {
|
||||
|
||||
let qr1;
|
||||
|
||||
test('can scan a QR code from bytes', async () => {
|
||||
test("can scan a QR code from bytes", async () => {
|
||||
const scan = QrCodeScan.fromBytes(qr2.toBytes());
|
||||
|
||||
expect(scan).toBeInstanceOf(QrCodeScan);
|
||||
@@ -819,50 +891,58 @@ describe('Key Verification', () => {
|
||||
expect(qr1.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => {
|
||||
test("can start a QR verification/reciprocate (`m.key.verification.start`)", async () => {
|
||||
let outgoingVerificationRequest = qr1.reciprocate();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][deviceId2.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId2.toString()][
|
||||
deviceId2.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been scanned', () => {
|
||||
test("can confirm QR code has been scanned", () => {
|
||||
expect(qr2.hasBeenScanned()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can confirm scanning (`m.key.verification.done`)', async () => {
|
||||
test("can confirm scanning (`m.key.verification.done`)", async () => {
|
||||
let outgoingVerificationRequest = qr2.confirmScanning();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.done");
|
||||
|
||||
const toDeviceEvents = [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][deviceId1.toString()],
|
||||
}];
|
||||
const toDeviceEvents = [
|
||||
{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: JSON.parse(outgoingVerificationRequest.body).messages[userId1.toString()][
|
||||
deviceId1.toString()
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been confirmed', () => {
|
||||
test("can confirm QR code has been confirmed", () => {
|
||||
expect(qr2.hasBeenConfirmed()).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationMethod', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("VerificationMethod", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(VerificationMethod.SasV1).toStrictEqual(0);
|
||||
expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1);
|
||||
expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2);
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const {
|
||||
EncryptionAlgorithm,
|
||||
EncryptionSettings,
|
||||
HistoryVisibility,
|
||||
VerificationState,
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("EncryptionAlgorithm", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
|
||||
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptionSettings.name, () => {
|
||||
test('can be instantiated with default values', () => {
|
||||
test("can be instantiated with default values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
|
||||
@@ -17,18 +22,20 @@ describe(EncryptionSettings.name, () => {
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
|
||||
});
|
||||
|
||||
test('checks the history visibility values', () => {
|
||||
test("checks the history visibility values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
es.historyVisibility = HistoryVisibility.Invited;
|
||||
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
|
||||
expect(() => { es.historyVisibility = 42 }).toThrow();
|
||||
expect(() => {
|
||||
es.historyVisibility = 42;
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationState', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("VerificationState", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(VerificationState.Trusted).toStrictEqual(0);
|
||||
expect(VerificationState.Untrusted).toStrictEqual(1);
|
||||
expect(VerificationState.UnknownDevice).toStrictEqual(2);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { HistoryVisibility } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("HistoryVisibility", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(HistoryVisibility.Invited).toStrictEqual(0);
|
||||
expect(HistoryVisibility.Joined).toStrictEqual(1);
|
||||
expect(HistoryVisibility.Shared).toStrictEqual(2);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
function* zip(...arrays) {
|
||||
const len = Math.min(...arrays.map((array) => array.length));
|
||||
|
||||
for (let nth = 0; nth < len; ++nth) {
|
||||
yield [...arrays.map((array) => array.at(nth))]
|
||||
yield [...arrays.map((array) => array.at(nth))];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ async function addMachineToMachine(machineToAdd, machine) {
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
|
||||
@@ -38,12 +40,16 @@ async function addMachineToMachine(machineToAdd, machine) {
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
one_time_key_counts: {
|
||||
curve25519: 10,
|
||||
signed_curve25519: 20,
|
||||
},
|
||||
});
|
||||
const marked = await machineToAdd.markRequestAsSent(outgoingRequests[0].id, outgoingRequests[0].type, hypothetical_response);
|
||||
const marked = await machineToAdd.markRequestAsSent(
|
||||
outgoingRequests[0].id,
|
||||
outgoingRequests[0].type,
|
||||
hypothetical_response,
|
||||
);
|
||||
expect(marked).toStrictEqual(true);
|
||||
|
||||
keysUploadRequest = outgoingRequests[0];
|
||||
@@ -71,7 +77,11 @@ async function addMachineToMachine(machineToAdd, machine) {
|
||||
keyQueryResponse.self_signing_keys[userId] = keys.self_signing_key;
|
||||
keyQueryResponse.user_signing_keys[userId] = keys.user_signing_key;
|
||||
|
||||
const marked = await machine.markRequestAsSent(outgoingRequests[1].id, outgoingRequests[1].type, JSON.stringify(keyQueryResponse));
|
||||
const marked = await machine.markRequestAsSent(
|
||||
outgoingRequests[1].id,
|
||||
outgoingRequests[1].type,
|
||||
JSON.stringify(keyQueryResponse),
|
||||
);
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,65 +7,75 @@ const {
|
||||
RoomId,
|
||||
ServerName,
|
||||
UserId,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe(UserId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new UserId('@foobar') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new UserId("@foobar");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
const user = new UserId('@foo:bar.org');
|
||||
const user = new UserId("@foo:bar.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(user.localpart).toStrictEqual('foo');
|
||||
test("localpart is present", () => {
|
||||
expect(user.localpart).toStrictEqual("foo");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(user.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('user ID is not historical', () => {
|
||||
test("user ID is not historical", () => {
|
||||
expect(user.isHistorical()).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can read the user ID as a string', () => {
|
||||
expect(user.toString()).toStrictEqual('@foo:bar.org');
|
||||
})
|
||||
test("can read the user ID as a string", () => {
|
||||
expect(user.toString()).toStrictEqual("@foo:bar.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe(DeviceId.name, () => {
|
||||
const device = new DeviceId('foo');
|
||||
const device = new DeviceId("foo");
|
||||
|
||||
test('can read the device ID as a string', () => {
|
||||
expect(device.toString()).toStrictEqual('foo');
|
||||
})
|
||||
test("can read the device ID as a string", () => {
|
||||
expect(device.toString()).toStrictEqual("foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe(DeviceKeyId.name, () => {
|
||||
for (const deviceKey of [
|
||||
{ name: 'ed25519',
|
||||
id: 'ed25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: 'ed25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "ed25519",
|
||||
id: "ed25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: "ed25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'curve25519',
|
||||
id: 'curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: 'curve25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "curve25519",
|
||||
id: "curve25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: "curve25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'signed curve25519',
|
||||
id: 'signed_curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: 'signed_curve25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "signed curve25519",
|
||||
id: "signed_curve25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: "signed_curve25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'unknown',
|
||||
id: 'hello:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: 'hello',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "unknown",
|
||||
id: "hello:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: "hello",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
]) {
|
||||
test(`${deviceKey.name} algorithm`, () => {
|
||||
const dk = new DeviceKeyId(deviceKey.id);
|
||||
@@ -78,8 +88,8 @@ describe(DeviceKeyId.name, () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('DeviceKeyAlgorithmName', () => {
|
||||
test('has the correct variants', () => {
|
||||
describe("DeviceKeyAlgorithmName", () => {
|
||||
test("has the correct variants", () => {
|
||||
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
|
||||
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
|
||||
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
|
||||
@@ -88,94 +98,100 @@ describe('DeviceKeyAlgorithmName', () => {
|
||||
});
|
||||
|
||||
describe(RoomId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new RoomId('!foo') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new RoomId("!foo");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
const room = new RoomId('!foo:bar.org');
|
||||
const room = new RoomId("!foo:bar.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('foo');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("foo");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('!foo:bar.org');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("!foo:bar.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe(ServerName.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new ServerName('@foobar') }).toThrow()
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new ServerName("@foobar");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('host is present', () => {
|
||||
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
|
||||
test("host is present", () => {
|
||||
expect(new ServerName("foo.org").host).toStrictEqual("foo.org");
|
||||
});
|
||||
|
||||
test('port can be optional', () => {
|
||||
expect(new ServerName('foo.org').port).toStrictEqual(undefined);
|
||||
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
|
||||
test("port can be optional", () => {
|
||||
expect(new ServerName("foo.org").port).toStrictEqual(undefined);
|
||||
expect(new ServerName("foo.org:1234").port).toStrictEqual(1234);
|
||||
});
|
||||
|
||||
test('server is not an IP literal', () => {
|
||||
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
|
||||
test("server is not an IP literal", () => {
|
||||
expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EventId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new EventId('%foo') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new EventId("%foo");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
describe('Versions 1 & 2', () => {
|
||||
const room = new EventId('$h29iv0s8:foo.org');
|
||||
describe("Versions 1 & 2", () => {
|
||||
const room = new EventId("$h29iv0s8:foo.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('h29iv0s8');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("h29iv0s8");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$h29iv0s8:foo.org');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("$h29iv0s8:foo.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 3', () => {
|
||||
const room = new EventId('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
describe("Version 3", () => {
|
||||
const room = new EventId("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 4', () => {
|
||||
const room = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
describe("Version 4", () => {
|
||||
const room = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -22,26 +22,28 @@ const {
|
||||
UserIdentity,
|
||||
VerificationRequest,
|
||||
VerificationState,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { addMachineToMachine } = require('./helper');
|
||||
require('fake-indexeddb/auto');
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
const { addMachineToMachine } = require("./helper");
|
||||
require("fake-indexeddb/auto");
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
test('can be instantiated with the async initializer', async () => {
|
||||
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
|
||||
test("can be instantiated with the async initializer", async () => {
|
||||
expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
test('can be instantiated with a store', async () => {
|
||||
let store_name = 'hello';
|
||||
let store_passphrase = 'world';
|
||||
test("can be instantiated with a store", async () => {
|
||||
let store_name = "hello";
|
||||
let store_passphrase = "world";
|
||||
|
||||
const by_store_name = db => db.name.startsWith(store_name);
|
||||
const by_store_name = (db) => db.name.startsWith(store_name);
|
||||
|
||||
// No databases.
|
||||
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
|
||||
|
||||
// Creating a new Olm machine.
|
||||
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Oh, there is 2 databases now, prefixed by `store_name`.
|
||||
let databases = (await indexedDB.databases()).filter(by_store_name);
|
||||
@@ -53,21 +55,28 @@ describe(OlmMachine.name, () => {
|
||||
]);
|
||||
|
||||
// Creating a new Olm machine, with the stored state.
|
||||
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Same number of databases.
|
||||
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe('cannot be instantiated with a store', () => {
|
||||
test('store name is missing', async () => {
|
||||
describe("cannot be instantiated with a store", () => {
|
||||
test("store name is missing", async () => {
|
||||
let store_name = null;
|
||||
let store_passphrase = 'world';
|
||||
let store_passphrase = "world";
|
||||
|
||||
let err = null;
|
||||
|
||||
try {
|
||||
await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
store_name,
|
||||
store_passphrase,
|
||||
);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
@@ -75,14 +84,19 @@ describe(OlmMachine.name, () => {
|
||||
expect(err).toBeDefined();
|
||||
});
|
||||
|
||||
test('store passphrase is missing', async () => {
|
||||
let store_name = 'hello';
|
||||
test("store passphrase is missing", async () => {
|
||||
let store_name = "hello";
|
||||
let store_passphrase = null;
|
||||
|
||||
let err = null;
|
||||
|
||||
try {
|
||||
await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
store_name,
|
||||
store_passphrase,
|
||||
);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
@@ -91,30 +105,35 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("foobar");
|
||||
const room = new RoomId("!baz:matrix.org");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return OlmMachine.initialize(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can drop/close', async () => {
|
||||
test("can drop/close", async () => {
|
||||
m = await machine();
|
||||
m.close();
|
||||
});
|
||||
|
||||
test('can drop/close with a store', async () => {
|
||||
let store_name = 'temporary';
|
||||
let store_passphrase = 'temporary';
|
||||
test("can drop/close with a store", async () => {
|
||||
let store_name = "temporary";
|
||||
let store_passphrase = "temporary";
|
||||
|
||||
const by_store_name = db => db.name.startsWith(store_name);
|
||||
const by_store_name = (db) => db.name.startsWith(store_name);
|
||||
|
||||
// No databases.
|
||||
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
|
||||
|
||||
// Creating a new Olm machine.
|
||||
const m = await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase);
|
||||
const m = await OlmMachine.initialize(
|
||||
new UserId("@foo:bar.org"),
|
||||
new DeviceId("baz"),
|
||||
store_name,
|
||||
store_passphrase,
|
||||
);
|
||||
expect(m).toBeInstanceOf(OlmMachine);
|
||||
|
||||
// Oh, there is 2 databases now, prefixed by `store_name`.
|
||||
@@ -133,31 +152,35 @@ describe(OlmMachine.name, () => {
|
||||
for (const database_name of [`${store_name}::matrix-sdk-crypto`, `${store_name}::matrix-sdk-crypto-meta`]) {
|
||||
const deleting = indexedDB.deleteDatabase(database_name);
|
||||
deleting.onsuccess = () => {};
|
||||
deleting.onerror = () => { throw new Error('failed to remove the database (error)') };
|
||||
deleting.onblocked = () => { throw new Error('failed to remove the database (blocked)') };
|
||||
deleting.onerror = () => {
|
||||
throw new Error("failed to remove the database (error)");
|
||||
};
|
||||
deleting.onblocked = () => {
|
||||
throw new Error("failed to remove the database (blocked)");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
test('can read user ID', async () => {
|
||||
test("can read user ID", async () => {
|
||||
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
|
||||
});
|
||||
|
||||
test('can read device ID', async () => {
|
||||
test("can read device ID", async () => {
|
||||
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
|
||||
});
|
||||
|
||||
test('can read identity keys', async () => {
|
||||
test("can read identity keys", async () => {
|
||||
const identityKeys = (await machine()).identityKeys;
|
||||
|
||||
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
});
|
||||
|
||||
test('can read display name', async () => {
|
||||
test("can read display name", async () => {
|
||||
expect(await machine().displayName).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can read tracked users', async () => {
|
||||
test("can read tracked users", async () => {
|
||||
const m = await machine();
|
||||
const trackedUsers = await m.trackedUsers();
|
||||
|
||||
@@ -165,32 +188,36 @@ describe(OlmMachine.name, () => {
|
||||
expect(trackedUsers.size).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('can update tracked users', async () => {
|
||||
test("can update tracked users", async () => {
|
||||
const m = await machine();
|
||||
|
||||
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test('can receive sync changes', async () => {
|
||||
test("can receive sync changes", async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
});
|
||||
|
||||
test('can get the outgoing requests that need to be send out', async () => {
|
||||
test("can get the outgoing requests that need to be send out", async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
|
||||
@@ -222,35 +249,40 @@ describe(OlmMachine.name, () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('setup workflow to mark requests as sent', () => {
|
||||
describe("setup workflow to mark requests as sent", () => {
|
||||
let m;
|
||||
let ougoingRequests;
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
|
||||
m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID"));
|
||||
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
|
||||
const receiveSyncChanges = await m.receiveSyncChanges(
|
||||
toDeviceEvents,
|
||||
changedDevices,
|
||||
oneTimeKeyCounts,
|
||||
unusedFallbackKeys,
|
||||
);
|
||||
outgoingRequests = await m.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('can mark requests as sent', async () => {
|
||||
test("can mark requests as sent", async () => {
|
||||
{
|
||||
const request = outgoingRequests[0];
|
||||
expect(request).toBeInstanceOf(KeysUploadRequest);
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
one_time_key_counts: {
|
||||
curve25519: 10,
|
||||
signed_curve25519: 20,
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
@@ -262,31 +294,29 @@ describe(OlmMachine.name, () => {
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"keys": {
|
||||
JLAFKJWSCS: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "JLAFKJWSCS",
|
||||
keys: {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
|
||||
},
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
unsigned: {
|
||||
device_display_name: "Alice's mobile phone",
|
||||
},
|
||||
"user_id": "@alice:example.org"
|
||||
}
|
||||
}
|
||||
user_id: "@alice:example.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
@@ -294,122 +324,121 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup workflow to encrypt/decrypt events', () => {
|
||||
describe("setup workflow to encrypt/decrypt events", () => {
|
||||
let m;
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('JLAFKJWSCS');
|
||||
const room = new RoomId('!test:localhost');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("JLAFKJWSCS");
|
||||
const room = new RoomId("!test:localhost");
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test('can pass keysquery and keysclaim requests directly', async () => {
|
||||
test("can pass keysquery and keysclaim requests directly", async () => {
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "AFGUOBTZWM",
|
||||
"keys": {
|
||||
AFGUOBTZWM: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "AFGUOBTZWM",
|
||||
keys: {
|
||||
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
|
||||
}
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA",
|
||||
},
|
||||
},
|
||||
user_id: "@example:localhost",
|
||||
unsigned: {
|
||||
device_display_name: "rust-sdk",
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "rust-sdk"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
failures: {},
|
||||
master_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["master"],
|
||||
keys: {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:TCSJXPWGVS":
|
||||
"+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"self_signing_keys": {
|
||||
self_signing_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["self_signing"],
|
||||
keys: {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI":
|
||||
"kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"user_signing_keys": {
|
||||
user_signing_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["user_signing"],
|
||||
keys: {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s":
|
||||
"g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_keys": {
|
||||
one_time_keys: {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
AFGUOBTZWM: {
|
||||
"signed_curve25519:AAAABQ": {
|
||||
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
"signatures": {
|
||||
key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test('can share a room key', async () => {
|
||||
const other_users = [new UserId('@example:localhost')];
|
||||
test("can share a room key", async () => {
|
||||
const other_users = [new UserId("@example:localhost")];
|
||||
|
||||
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
|
||||
|
||||
@@ -417,20 +446,22 @@ describe(OlmMachine.name, () => {
|
||||
expect(requests[0].event_type).toBeDefined();
|
||||
expect(requests[0].txn_id).toBeDefined();
|
||||
expect(requests[0].messages).toBeDefined();
|
||||
expect(requests[0].messages['@example:localhost']).toBeDefined();
|
||||
expect(requests[0].messages["@example:localhost"]).toBeDefined();
|
||||
});
|
||||
|
||||
let encrypted;
|
||||
|
||||
test('can encrypt an event', async () => {
|
||||
encrypted = JSON.parse(await m.encryptRoomEvent(
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello, World!"
|
||||
}),
|
||||
));
|
||||
test("can encrypt an event", async () => {
|
||||
encrypted = JSON.parse(
|
||||
await m.encryptRoomEvent(
|
||||
room,
|
||||
"m.room.message",
|
||||
JSON.stringify({
|
||||
msgtype: "m.text",
|
||||
body: "Hello, World!",
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(encrypted.algorithm).toBeDefined();
|
||||
expect(encrypted.ciphertext).toBeDefined();
|
||||
@@ -439,17 +470,17 @@ describe(OlmMachine.name, () => {
|
||||
expect(encrypted.session_id).toBeDefined();
|
||||
});
|
||||
|
||||
test('can decrypt an event', async () => {
|
||||
test("can decrypt an event", async () => {
|
||||
const decrypted = await m.decryptRoomEvent(
|
||||
JSON.stringify({
|
||||
"type": "m.room.encrypted",
|
||||
"event_id": "$xxxxx:example.org",
|
||||
"origin_server_ts": Date.now(),
|
||||
"sender": user.toString(),
|
||||
type: "m.room.encrypted",
|
||||
event_id: "$xxxxx:example.org",
|
||||
origin_server_ts: Date.now(),
|
||||
sender: user.toString(),
|
||||
content: encrypted,
|
||||
unsigned: {
|
||||
"age": 1234
|
||||
}
|
||||
age: 1234,
|
||||
},
|
||||
}),
|
||||
room,
|
||||
);
|
||||
@@ -484,7 +515,7 @@ describe(OlmMachine.name, () => {
|
||||
await expect(() => m.decryptRoomEvent(JSON.stringify(evt), room)).rejects.toThrowError();
|
||||
});
|
||||
|
||||
test('can read cross-signing status', async () => {
|
||||
test("can read cross-signing status", async () => {
|
||||
const m = await machine();
|
||||
const crossSigningStatus = await m.crossSigningStatus();
|
||||
|
||||
@@ -494,9 +525,9 @@ describe(OlmMachine.name, () => {
|
||||
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can sign a message', async () => {
|
||||
test("can sign a message", async () => {
|
||||
const m = await machine();
|
||||
const signatures = await m.sign('foo');
|
||||
const signatures = await m.sign("foo");
|
||||
|
||||
expect(signatures.isEmpty()).toStrictEqual(false);
|
||||
expect(signatures.count).toStrictEqual(1);
|
||||
@@ -507,9 +538,9 @@ describe(OlmMachine.name, () => {
|
||||
{
|
||||
const signature = signatures.get(user);
|
||||
|
||||
expect(signature.has('ed25519:foobar')).toStrictEqual(true);
|
||||
expect(signature.has("ed25519:foobar")).toStrictEqual(true);
|
||||
|
||||
const s = signature.get('ed25519:foobar');
|
||||
const s = signature.get("ed25519:foobar");
|
||||
|
||||
expect(s).toBeInstanceOf(MaybeSignature);
|
||||
|
||||
@@ -525,18 +556,18 @@ describe(OlmMachine.name, () => {
|
||||
|
||||
// `getSignature`
|
||||
{
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar"));
|
||||
expect(signature.toBase64()).toStrictEqual(base64);
|
||||
}
|
||||
|
||||
// Unknown signatures.
|
||||
{
|
||||
expect(signatures.get(new UserId('@hello:example.org'))).toBeUndefined();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeUndefined();
|
||||
expect(signatures.get(new UserId("@hello:example.org"))).toBeUndefined();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test('can get a user identities', async () => {
|
||||
test("can get a user identities", async () => {
|
||||
const m = await machine();
|
||||
let _ = m.bootstrapCrossSigning(true);
|
||||
|
||||
@@ -556,15 +587,15 @@ describe(OlmMachine.name, () => {
|
||||
expect(isTrusted).toStrictEqual(false);
|
||||
});
|
||||
|
||||
describe('can export/import room keys', () => {
|
||||
describe("can export/import room keys", () => {
|
||||
let m;
|
||||
let exportedRoomKeys;
|
||||
|
||||
test('can export room keys', async () => {
|
||||
test("can export room keys", async () => {
|
||||
m = await machine();
|
||||
await m.shareRoomKey(room, [new UserId('@bob:example.org')], new EncryptionSettings());
|
||||
await m.shareRoomKey(room, [new UserId("@bob:example.org")], new EncryptionSettings());
|
||||
|
||||
exportedRoomKeys = await m.exportRoomKeys(session => {
|
||||
exportedRoomKeys = await m.exportRoomKeys((session) => {
|
||||
expect(session).toBeInstanceOf(InboundGroupSession);
|
||||
expect(session.roomId.toString()).toStrictEqual(room.toString());
|
||||
expect(session.sessionId).toBeDefined();
|
||||
@@ -589,9 +620,9 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
|
||||
let encryptedExportedRoomKeys;
|
||||
let encryptionPassphrase = 'Hello, Matrix!';
|
||||
let encryptionPassphrase = "Hello, Matrix!";
|
||||
|
||||
test('can encrypt the exported room keys', () => {
|
||||
test("can encrypt the exported room keys", () => {
|
||||
encryptedExportedRoomKeys = OlmMachine.encryptExportedRoomKeys(
|
||||
exportedRoomKeys,
|
||||
encryptionPassphrase,
|
||||
@@ -601,7 +632,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(encryptedExportedRoomKeys).toMatch(/^-----BEGIN MEGOLM SESSION DATA-----/);
|
||||
});
|
||||
|
||||
test('can decrypt the exported room keys', () => {
|
||||
test("can decrypt the exported room keys", () => {
|
||||
const decryptedExportedRoomKeys = OlmMachine.decryptExportedRoomKeys(
|
||||
encryptedExportedRoomKeys,
|
||||
encryptionPassphrase,
|
||||
@@ -610,7 +641,7 @@ describe(OlmMachine.name, () => {
|
||||
expect(decryptedExportedRoomKeys).toStrictEqual(exportedRoomKeys);
|
||||
});
|
||||
|
||||
test('can import room keys', async () => {
|
||||
test("can import room keys", async () => {
|
||||
const progressListener = (progress, total) => {
|
||||
expect(progress).toBeLessThan(total);
|
||||
|
||||
@@ -629,169 +660,160 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('can do in-room verification', () => {
|
||||
describe("can do in-room verification", () => {
|
||||
let m;
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('JLAFKJWSCS');
|
||||
const room = new RoomId('!test:localhost');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("JLAFKJWSCS");
|
||||
const room = new RoomId("!test:localhost");
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test('can inject devices from someone else', async () => {
|
||||
test("can inject devices from someone else", async () => {
|
||||
{
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ATRLDCRXAC": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "ATRLDCRXAC",
|
||||
"keys": {
|
||||
ATRLDCRXAC: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "ATRLDCRXAC",
|
||||
keys: {
|
||||
"curve25519:ATRLDCRXAC": "cAVT5Es3Z3F5pFD+2w3HT7O9+R3PstzYVkzD51X/FWQ",
|
||||
"ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA"
|
||||
"ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ATRLDCRXAC": "ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ"
|
||||
}
|
||||
"ed25519:ATRLDCRXAC":
|
||||
"ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ",
|
||||
},
|
||||
},
|
||||
user_id: "@example:morpheus.localhost",
|
||||
unsigned: {
|
||||
device_display_name: "Element Desktop: Linux",
|
||||
},
|
||||
"user_id": "@example:morpheus.localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "Element Desktop: Linux"
|
||||
}
|
||||
},
|
||||
"EYYGYTCTNC": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "EYYGYTCTNC",
|
||||
"keys": {
|
||||
EYYGYTCTNC: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "EYYGYTCTNC",
|
||||
keys: {
|
||||
"curve25519:EYYGYTCTNC": "Pqu50fo472wgb6NjKkaUxjuqoAIEAmhln2gw/zSQ7Ek",
|
||||
"ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU"
|
||||
"ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:EYYGYTCTNC": "pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ"
|
||||
}
|
||||
"ed25519:EYYGYTCTNC":
|
||||
"pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ",
|
||||
},
|
||||
},
|
||||
user_id: "@example:morpheus.localhost",
|
||||
unsigned: {
|
||||
device_display_name: "WeeChat-Matrix-rs",
|
||||
},
|
||||
"user_id": "@example:morpheus.localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "WeeChat-Matrix-rs"
|
||||
}
|
||||
},
|
||||
"SUMODVLSIU": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "SUMODVLSIU",
|
||||
"keys": {
|
||||
SUMODVLSIU: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "SUMODVLSIU",
|
||||
keys: {
|
||||
"curve25519:SUMODVLSIU": "geQXWGWc++gcUHk0JcFmEVSjyzDOnk2mjVsUQwbNqQU",
|
||||
"ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs"
|
||||
"ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:SUMODVLSIU": "Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg",
|
||||
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM": "Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ"
|
||||
}
|
||||
"ed25519:SUMODVLSIU":
|
||||
"Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg",
|
||||
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
|
||||
"Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ",
|
||||
},
|
||||
},
|
||||
user_id: "@example:morpheus.localhost",
|
||||
unsigned: {
|
||||
device_display_name: "Element Desktop (Linux)",
|
||||
},
|
||||
"user_id": "@example:morpheus.localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "Element Desktop (Linux)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
"@example:morpheus.localhost": {
|
||||
"user_id": "@example:morpheus.localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": "ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:SUMODVLSIU": "RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"self_signing_keys": {
|
||||
failures: {},
|
||||
master_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
"user_id": "@example:morpheus.localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM": "rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM"
|
||||
user_id: "@example:morpheus.localhost",
|
||||
usage: ["master"],
|
||||
keys: {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
|
||||
"ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": "uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:SUMODVLSIU":
|
||||
"RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"user_signing_keys": {
|
||||
self_signing_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
"user_id": "@example:morpheus.localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw": "GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw"
|
||||
user_id: "@example:morpheus.localhost",
|
||||
usage: ["self_signing"],
|
||||
keys: {
|
||||
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
|
||||
"rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A": "4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
|
||||
"uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user_signing_keys: {
|
||||
"@example:morpheus.localhost": {
|
||||
user_id: "@example:morpheus.localhost",
|
||||
usage: ["user_signing"],
|
||||
keys: {
|
||||
"ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw":
|
||||
"GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw",
|
||||
},
|
||||
signatures: {
|
||||
"@example:morpheus.localhost": {
|
||||
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
|
||||
"4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test('can start an in-room SAS verification', async () => {
|
||||
test("can start an in-room SAS verification", async () => {
|
||||
let _ = m.bootstrapCrossSigning(true);
|
||||
const identity = await m.getIdentity(new UserId('@example:morpheus.localhost'));
|
||||
const identity = await m.getIdentity(new UserId("@example:morpheus.localhost"));
|
||||
|
||||
expect(identity).toBeInstanceOf(UserIdentity);
|
||||
expect(identity.isVerified()).toStrictEqual(false);
|
||||
|
||||
const eventId = new EventId('$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg');
|
||||
const eventId = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
|
||||
const verificationRequest = await identity.requestVerification(room, eventId);
|
||||
expect(verificationRequest).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
await m.receiveVerificationEvent(
|
||||
JSON.stringify({
|
||||
"sender": "@example:morpheus.localhost",
|
||||
"type": "m.key.verification.ready",
|
||||
"event_id": "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0",
|
||||
"origin_server_ts": 1674037263075,
|
||||
"content": {
|
||||
"methods": [
|
||||
"m.sas.v1",
|
||||
"m.qr_code.show.v1",
|
||||
"m.reciprocate.v1"
|
||||
],
|
||||
sender: "@example:morpheus.localhost",
|
||||
type: "m.key.verification.ready",
|
||||
event_id: "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0",
|
||||
origin_server_ts: 1674037263075,
|
||||
content: {
|
||||
"methods": ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"],
|
||||
"from_device": "SUMODVLSIU",
|
||||
"m.relates_to": {
|
||||
"rel_type": "m.reference",
|
||||
"event_id": eventId.toString(),
|
||||
}
|
||||
}
|
||||
rel_type: "m.reference",
|
||||
event_id: eventId.toString(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
room
|
||||
room,
|
||||
);
|
||||
|
||||
expect(verificationRequest.roomId.toString()).toStrictEqual(room.toString());
|
||||
@@ -802,22 +824,22 @@ describe(OlmMachine.name, () => {
|
||||
expect(outgoingVerificationRequest.id).toBeDefined();
|
||||
expect(outgoingVerificationRequest.room_id).toStrictEqual(room.toString());
|
||||
expect(outgoingVerificationRequest.txn_id).toBeDefined();
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
|
||||
expect(outgoingVerificationRequest.body).toBeDefined();
|
||||
|
||||
const body = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(body).toMatchObject({
|
||||
from_device: expect.any(String),
|
||||
method: 'm.sas.v1',
|
||||
key_agreement_protocols: [expect.any(String)],
|
||||
hashes: [expect.any(String)],
|
||||
message_authentication_codes: [expect.any(String), expect.any(String)],
|
||||
short_authentication_string: ['decimal', 'emoji'],
|
||||
'm.relates_to': {
|
||||
rel_type: 'm.reference',
|
||||
"from_device": expect.any(String),
|
||||
"method": "m.sas.v1",
|
||||
"key_agreement_protocols": [expect.any(String)],
|
||||
"hashes": [expect.any(String)],
|
||||
"message_authentication_codes": [expect.any(String), expect.any(String)],
|
||||
"short_authentication_string": ["decimal", "emoji"],
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: eventId.toString(),
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const {
|
||||
RequestType,
|
||||
KeysUploadRequest,
|
||||
KeysQueryRequest,
|
||||
KeysClaimRequest,
|
||||
ToDeviceRequest,
|
||||
SignatureUploadRequest,
|
||||
RoomMessageRequest,
|
||||
KeysBackupRequest,
|
||||
} = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('RequestType', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("RequestType", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(RequestType.KeysUpload).toStrictEqual(0);
|
||||
expect(RequestType.KeysQuery).toStrictEqual(1);
|
||||
expect(RequestType.KeysClaim).toStrictEqual(2);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { DeviceLists, UserId } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe(DeviceLists.name, () => {
|
||||
test('can be empty', () => {
|
||||
test("can be empty", () => {
|
||||
const empty = new DeviceLists();
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
@@ -9,7 +9,7 @@ describe(DeviceLists.name, () => {
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('can be coerced empty', () => {
|
||||
test("can be coerced empty", () => {
|
||||
const empty = new DeviceLists([], []);
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
@@ -17,15 +17,15 @@ describe(DeviceLists.name, () => {
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('returns the correct `changed` and `left`', () => {
|
||||
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
|
||||
test("returns the correct `changed` and `left`", () => {
|
||||
const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]);
|
||||
|
||||
expect(list.isEmpty()).toStrictEqual(false);
|
||||
|
||||
expect(list.changed).toHaveLength(1);
|
||||
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
|
||||
expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org");
|
||||
|
||||
expect(list.left).toHaveLength(1);
|
||||
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
|
||||
expect(list.left[0].toString()).toStrictEqual("@baz:qux.org");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require("../pkg/matrix_sdk_crypto_js");
|
||||
|
||||
describe('LoggerLevel', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("LoggerLevel", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(LoggerLevel.Trace).toStrictEqual(0);
|
||||
expect(LoggerLevel.Debug).toStrictEqual(1);
|
||||
expect(LoggerLevel.Info).toStrictEqual(2);
|
||||
@@ -14,7 +14,7 @@ describe(Tracing.name, () => {
|
||||
if (Tracing.isAvailable()) {
|
||||
let tracing = new Tracing(LoggerLevel.Debug);
|
||||
|
||||
test('can installed several times', () => {
|
||||
test("can installed several times", () => {
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
new Tracing(LoggerLevel.Warn);
|
||||
new Tracing(LoggerLevel.Debug);
|
||||
@@ -23,27 +23,30 @@ describe(Tracing.name, () => {
|
||||
const originalConsoleDebug = console.debug;
|
||||
|
||||
for (const [testName, testPreState, testPostState, expectedGotcha] of [
|
||||
["can log something", () => {}, () => {}, true],
|
||||
[
|
||||
'can log something',
|
||||
() => {},
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
[
|
||||
'can change the logger level',
|
||||
() => { tracing.minLevel = LoggerLevel.Warn },
|
||||
() => { tracing.minLevel = LoggerLevel.Debug },
|
||||
"can change the logger level",
|
||||
() => {
|
||||
tracing.minLevel = LoggerLevel.Warn;
|
||||
},
|
||||
() => {
|
||||
tracing.minLevel = LoggerLevel.Debug;
|
||||
},
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
"can be turned off",
|
||||
() => {
|
||||
tracing.turnOff();
|
||||
},
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
[
|
||||
'can be turned on',
|
||||
() => { tracing.turnOn() },
|
||||
"can be turned on",
|
||||
() => {
|
||||
tracing.turnOn();
|
||||
},
|
||||
() => {},
|
||||
true,
|
||||
],
|
||||
@@ -51,8 +54,10 @@ describe(Tracing.name, () => {
|
||||
// This one *must* be the last. We are turning tracing off
|
||||
// again for the other tests.
|
||||
[
|
||||
'can be turned off',
|
||||
() => { tracing.turnOff() },
|
||||
"can be turned off",
|
||||
() => {
|
||||
tracing.turnOff();
|
||||
},
|
||||
() => {},
|
||||
false,
|
||||
],
|
||||
@@ -68,7 +73,7 @@ describe(Tracing.name, () => {
|
||||
};
|
||||
|
||||
// Do something that emits a `DEBUG` log.
|
||||
await OlmMachine.initialize(new UserId('@alice:example.org'), new DeviceId('foo'));
|
||||
await OlmMachine.initialize(new UserId("@alice:example.org"), new DeviceId("foo"));
|
||||
|
||||
console.debug = originalConsoleDebug;
|
||||
testPostState();
|
||||
@@ -77,8 +82,10 @@ describe(Tracing.name, () => {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
test('cannot be constructed', () => {
|
||||
expect(() => { new Tracing(LoggerLevel.Error) }).toThrow();
|
||||
test("cannot be constructed", () => {
|
||||
expect(() => {
|
||||
new Tracing(LoggerLevel.Error);
|
||||
}).toThrow();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
"readme": "README.md"
|
||||
}
|
||||
}
|
||||
|
||||
1
bindings/matrix-sdk-crypto-nodejs/.prettierignore
Normal file
1
bindings/matrix-sdk-crypto-nodejs/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
/pkg
|
||||
9
bindings/matrix-sdk-crypto-nodejs/.prettierrc.js
Normal file
9
bindings/matrix-sdk-crypto-nodejs/.prettierrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// prettier configuration: the same as the conventions used throughout Matrix.org
|
||||
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
|
||||
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
quoteProps: "consistent",
|
||||
trailingComma: "all",
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 0.1.0-beta.1 - 2022-07-14
|
||||
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
|
||||
|
||||
## 0.1.0-beta.0 - 2022-07-12
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
|
||||
## Usage
|
||||
|
||||
Just add the latest release to your `package.json`:
|
||||
|
||||
```sh
|
||||
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
|
||||
```
|
||||
@@ -112,28 +113,27 @@ generated. At the same level of those files, you can edit a file and
|
||||
try this:
|
||||
|
||||
```javascript
|
||||
const { OlmMachine } = require('./index.js');
|
||||
const { OlmMachine } = require("./index.js");
|
||||
|
||||
// Let's see what we can do.
|
||||
```
|
||||
|
||||
The `OlmMachine` state machine works in a push/pull manner:
|
||||
|
||||
* You push state changes and events retrieved from a Matrix homeserver
|
||||
`/sync` response, into the state machine,
|
||||
|
||||
* You pull requests that you will need to send back to the homeserver
|
||||
out of the state machine.
|
||||
|
||||
- You push state changes and events retrieved from a Matrix homeserver
|
||||
`/sync` response, into the state machine,
|
||||
- You pull requests that you will need to send back to the homeserver
|
||||
out of the state machine.
|
||||
|
||||
```javascript
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require('./index.js');
|
||||
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require("./index.js");
|
||||
|
||||
async function main() {
|
||||
// Define a user ID.
|
||||
const alice = new UserId('@alice:example.org');
|
||||
const alice = new UserId("@alice:example.org");
|
||||
|
||||
// Define a device ID.
|
||||
const device = new DeviceId('DEVICEID');
|
||||
const device = new DeviceId("DEVICEID");
|
||||
|
||||
// Let's create the `OlmMachine` state machine.
|
||||
const machine = await OlmMachine.initialize(alice, device);
|
||||
@@ -198,8 +198,6 @@ $ npm run doc
|
||||
|
||||
The documentation is generated in the `./docs` directory.
|
||||
|
||||
|
||||
|
||||
[Node.js]: https://nodejs.org/
|
||||
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
|
||||
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { DownloaderHelper } = require('node-downloader-helper');
|
||||
const { HttpsProxyAgent } = require("https-proxy-agent");
|
||||
const { DownloaderHelper } = require("node-downloader-helper");
|
||||
const { version } = require("./package.json");
|
||||
const { platform, arch } = process
|
||||
const { platform, arch } = process;
|
||||
|
||||
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
|
||||
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
|
||||
|
||||
const byteHelper = function (value) {
|
||||
if (value === 0) {
|
||||
return '0 b';
|
||||
return "0 b";
|
||||
}
|
||||
const units = ['b', 'kB', 'MB', 'GB', 'TB'];
|
||||
const units = ["b", "kB", "MB", "GB", "TB"];
|
||||
const number = Math.floor(Math.log(value) / Math.log(1024));
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + ' ' +
|
||||
units[number];
|
||||
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + " " + units[number];
|
||||
};
|
||||
|
||||
function download_lib(libname) {
|
||||
@@ -33,9 +32,9 @@ function download_lib(libname) {
|
||||
});
|
||||
}
|
||||
|
||||
dl.on('end', () => console.info('Download Completed'));
|
||||
dl.on('error', (err) => console.info('Download Failed', err));
|
||||
dl.on('progress', stats => {
|
||||
dl.on("end", () => console.info("Download Completed"));
|
||||
dl.on("error", (err) => console.info("Download Failed", err));
|
||||
dl.on("progress", (stats) => {
|
||||
const progress = stats.progress.toFixed(1);
|
||||
const speed = byteHelper(stats.speed);
|
||||
const downloaded = byteHelper(stats.downloaded);
|
||||
@@ -49,74 +48,74 @@ function download_lib(libname) {
|
||||
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
|
||||
}
|
||||
});
|
||||
dl.start().catch(err => console.error(err));
|
||||
dl.start().catch((err) => console.error(err));
|
||||
}
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== "function") {
|
||||
try {
|
||||
return readFileSync("/usr/bin/ldd", "utf8").includes("musl");
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header;
|
||||
return !glibcVersionRuntime;
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.win32-x64-msvc.node')
|
||||
break
|
||||
case 'ia32':
|
||||
download_lib('matrix-sdk-crypto.win32-ia32-msvc.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.win32-arm64-msvc.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
download_lib('matrix-sdk-crypto.darwin-x64.node')
|
||||
break
|
||||
case 'arm64':
|
||||
download_lib('matrix-sdk-crypto.darwin-arm64.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-musl.node')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-x64-gnu.node')
|
||||
case "win32":
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
download_lib("matrix-sdk-crypto.win32-x64-msvc.node");
|
||||
break;
|
||||
case "ia32":
|
||||
download_lib("matrix-sdk-crypto.win32-ia32-msvc.node");
|
||||
break;
|
||||
case "arm64":
|
||||
download_lib("matrix-sdk-crypto.win32-arm64-msvc.node");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`);
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
throw new Error('Linux for arm64 musl isn\'t support at the moment')
|
||||
} else {
|
||||
download_lib('matrix-sdk-crypto.linux-arm64-gnu.node')
|
||||
break;
|
||||
case "darwin":
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
download_lib("matrix-sdk-crypto.darwin-x64.node");
|
||||
break;
|
||||
case "arm64":
|
||||
download_lib("matrix-sdk-crypto.darwin-arm64.node");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`);
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
download_lib('matrix-sdk-crypto.linux-arm-gnueabihf.node')
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
break;
|
||||
case "linux":
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
if (isMusl()) {
|
||||
download_lib("matrix-sdk-crypto.linux-x64-musl.node");
|
||||
} else {
|
||||
download_lib("matrix-sdk-crypto.linux-x64-gnu.node");
|
||||
}
|
||||
break;
|
||||
case "arm64":
|
||||
if (isMusl()) {
|
||||
throw new Error("Linux for arm64 musl isn't support at the moment");
|
||||
} else {
|
||||
download_lib("matrix-sdk-crypto.linux-arm64-gnu.node");
|
||||
}
|
||||
break;
|
||||
case "arm":
|
||||
download_lib("matrix-sdk-crypto.linux-arm-gnueabihf.node");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.9.0",
|
||||
"jest": "^28.1.0",
|
||||
"prettier": "^2.8.3",
|
||||
"typedoc": "^0.22.17",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
@@ -22,6 +23,7 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "prettier --check .",
|
||||
"release-build": "napi build --platform --release --strip",
|
||||
"build": "napi build --platform",
|
||||
"postinstall": "node download-lib.js",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -16,11 +17,38 @@ use crate::{
|
||||
sync_events, types, vodozemac,
|
||||
};
|
||||
|
||||
/// The value used by the `OlmMachine` JS class.
|
||||
///
|
||||
/// It has 2 states: `Opened` and `Closed`. Why maintaining the state here?
|
||||
/// Because NodeJS has no way to drop an object explicitly, and we want to be
|
||||
/// able to “close” the `OlmMachine` to free all associated data. More over,
|
||||
/// `napi-rs` doesn't allow a function to take the ownership of the type itself
|
||||
/// (`fn close(self) { … }`). So we manage the state ourselves.
|
||||
///
|
||||
/// Using the `OlmMachine` when its state is `Closed` will panic.
|
||||
enum OlmMachineInner {
|
||||
Opened(matrix_sdk_crypto::OlmMachine),
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl Deref for OlmMachineInner {
|
||||
type Target = matrix_sdk_crypto::OlmMachine;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Opened(machine) => machine,
|
||||
Self::Closed => panic!("The `OlmMachine` has been closed, cannot use it anymore"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
/// used for Matrix end to end encryption.
|
||||
// #[napi(custom_finalize)]
|
||||
#[napi]
|
||||
pub struct OlmMachine {
|
||||
inner: matrix_sdk_crypto::OlmMachine,
|
||||
inner: OlmMachineInner,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -77,7 +105,7 @@ impl OlmMachine {
|
||||
store_passphrase.zeroize();
|
||||
|
||||
Ok(OlmMachine {
|
||||
inner: match store {
|
||||
inner: OlmMachineInner::Opened(match store {
|
||||
Some(store) => matrix_sdk_crypto::OlmMachine::with_store(
|
||||
user_id.inner.as_ref(),
|
||||
device_id.inner.as_ref(),
|
||||
@@ -92,7 +120,7 @@ impl OlmMachine {
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -409,4 +437,21 @@ impl OlmMachine {
|
||||
pub async fn sign(&self, message: String) -> types::Signatures {
|
||||
self.inner.sign(message.as_str()).await.into()
|
||||
}
|
||||
|
||||
/// Shut down the `OlmMachine`.
|
||||
///
|
||||
/// The `OlmMachine` cannot be used after this method has been called,
|
||||
/// otherwise it will panic.
|
||||
///
|
||||
/// All associated resources will be closed too, like the crypto storage
|
||||
/// connections.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller is responsible to **not** use any objects that came from this
|
||||
/// `OlmMachine` after this `close` method has been called.
|
||||
#[napi(strict)]
|
||||
pub fn close(&mut self) {
|
||||
self.inner = OlmMachineInner::Closed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
const { Attachment, EncryptedAttachment } = require('../');
|
||||
const { Attachment, EncryptedAttachment } = require("../");
|
||||
|
||||
describe(Attachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
let encryptedAttachment;
|
||||
|
||||
test('can encrypt data', () => {
|
||||
|
||||
test("can encrypt data", () => {
|
||||
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
|
||||
|
||||
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
|
||||
|
||||
expect(mediaEncryptionInfo).toMatchObject({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: expect.any(String),
|
||||
key_ops: expect.arrayContaining(['encrypt', 'decrypt']),
|
||||
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
|
||||
alg: expect.any(String),
|
||||
k: expect.any(String),
|
||||
ext: expect.any(Boolean),
|
||||
},
|
||||
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
hashes: {
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/)
|
||||
}
|
||||
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
|
||||
},
|
||||
});
|
||||
|
||||
const encryptedData = encryptedAttachment.encryptedData;
|
||||
expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false);
|
||||
expect(
|
||||
encryptedData.every((i) => {
|
||||
i != 0;
|
||||
}),
|
||||
).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can decrypt data', () => {
|
||||
test("can decrypt data", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
|
||||
@@ -40,34 +44,36 @@ describe(Attachment.name, () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can only decrypt once', () => {
|
||||
test("can only decrypt once", () => {
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
|
||||
|
||||
expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow()
|
||||
expect(() => {
|
||||
textDecoder.decode(decryptedAttachment);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptedAttachment.name, () => {
|
||||
const originalData = 'hello';
|
||||
const originalData = "hello";
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
test('can be created manually', () => {
|
||||
test("can be created manually", () => {
|
||||
const encryptedAttachment = new EncryptedAttachment(
|
||||
new Uint8Array([24, 150, 67, 37, 144]),
|
||||
JSON.stringify({
|
||||
v: 'v2',
|
||||
v: "v2",
|
||||
key: {
|
||||
kty: 'oct',
|
||||
key_ops: [ 'encrypt', 'decrypt' ],
|
||||
alg: 'A256CTR',
|
||||
k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM',
|
||||
ext: true
|
||||
kty: "oct",
|
||||
key_ops: ["encrypt", "decrypt"],
|
||||
alg: "A256CTR",
|
||||
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
|
||||
ext: true,
|
||||
},
|
||||
iv: 'xk2AcWkomiYAAAAAAAAAAA',
|
||||
iv: "xk2AcWkomiYAAAAAAAAAAA",
|
||||
hashes: {
|
||||
sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok'
|
||||
}
|
||||
})
|
||||
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../');
|
||||
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require("../");
|
||||
|
||||
describe('EncryptionAlgorithm', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("EncryptionAlgorithm", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
|
||||
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(EncryptionSettings.name, () => {
|
||||
test('can be instantiated with default values', () => {
|
||||
test("can be instantiated with default values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
|
||||
@@ -17,18 +17,20 @@ describe(EncryptionSettings.name, () => {
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
|
||||
});
|
||||
|
||||
test('checks the history visibility values', () => {
|
||||
test("checks the history visibility values", () => {
|
||||
const es = new EncryptionSettings();
|
||||
|
||||
es.historyVisibility = HistoryVisibility.Invited;
|
||||
|
||||
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
|
||||
expect(() => { es.historyVisibility = 42 }).toThrow();
|
||||
expect(() => {
|
||||
es.historyVisibility = 42;
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationState', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("VerificationState", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(VerificationState.Trusted).toStrictEqual(0);
|
||||
expect(VerificationState.Untrusted).toStrictEqual(1);
|
||||
expect(VerificationState.UnknownDevice).toStrictEqual(2);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { HistoryVisibility } = require('../');
|
||||
const { HistoryVisibility } = require("../");
|
||||
|
||||
describe('HistoryVisibility', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("HistoryVisibility", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(HistoryVisibility.Invited).toStrictEqual(0);
|
||||
expect(HistoryVisibility.Joined).toStrictEqual(1);
|
||||
expect(HistoryVisibility.Shared).toStrictEqual(2);
|
||||
|
||||
@@ -1,62 +1,80 @@
|
||||
const { UserId, DeviceId, DeviceKeyId, DeviceKeyAlgorithm, DeviceKeyAlgorithmName, RoomId, ServerName } = require('../');
|
||||
const {
|
||||
UserId,
|
||||
DeviceId,
|
||||
DeviceKeyId,
|
||||
DeviceKeyAlgorithm,
|
||||
DeviceKeyAlgorithmName,
|
||||
RoomId,
|
||||
ServerName,
|
||||
} = require("../");
|
||||
|
||||
describe(UserId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new UserId('@foobar') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new UserId("@foobar");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
const user = new UserId('@foo:bar.org');
|
||||
const user = new UserId("@foo:bar.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(user.localpart).toStrictEqual('foo');
|
||||
test("localpart is present", () => {
|
||||
expect(user.localpart).toStrictEqual("foo");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(user.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('user ID is not historical', () => {
|
||||
test("user ID is not historical", () => {
|
||||
expect(user.isHistorical()).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can read the user ID as a string', () => {
|
||||
expect(user.toString()).toStrictEqual('@foo:bar.org');
|
||||
})
|
||||
test("can read the user ID as a string", () => {
|
||||
expect(user.toString()).toStrictEqual("@foo:bar.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe(DeviceId.name, () => {
|
||||
const device = new DeviceId('foo');
|
||||
const device = new DeviceId("foo");
|
||||
|
||||
test('can read the device ID as a string', () => {
|
||||
expect(device.toString()).toStrictEqual('foo');
|
||||
})
|
||||
test("can read the device ID as a string", () => {
|
||||
expect(device.toString()).toStrictEqual("foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe(DeviceKeyId.name, () => {
|
||||
for (const deviceKey of [
|
||||
{ name: 'ed25519',
|
||||
id: 'ed25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: 'ed25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "ed25519",
|
||||
id: "ed25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Ed25519,
|
||||
algorithm: "ed25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'curve25519',
|
||||
id: 'curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: 'curve25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "curve25519",
|
||||
id: "curve25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Curve25519,
|
||||
algorithm: "curve25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'signed curve25519',
|
||||
id: 'signed_curve25519:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: 'signed_curve25519',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "signed curve25519",
|
||||
id: "signed_curve25519:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
|
||||
algorithm: "signed_curve25519",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
|
||||
{ name: 'unknown',
|
||||
id: 'hello:foobar',
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: 'hello',
|
||||
deviceId: 'foobar' },
|
||||
{
|
||||
name: "unknown",
|
||||
id: "hello:foobar",
|
||||
algorithmName: DeviceKeyAlgorithmName.Unknown,
|
||||
algorithm: "hello",
|
||||
deviceId: "foobar",
|
||||
},
|
||||
]) {
|
||||
test(`${deviceKey.name} algorithm`, () => {
|
||||
const dk = new DeviceKeyId(deviceKey.id);
|
||||
@@ -69,8 +87,8 @@ describe(DeviceKeyId.name, () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('DeviceKeyAlgorithmName', () => {
|
||||
test('has the correct variants', () => {
|
||||
describe("DeviceKeyAlgorithmName", () => {
|
||||
test("has the correct variants", () => {
|
||||
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
|
||||
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
|
||||
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
|
||||
@@ -79,40 +97,44 @@ describe('DeviceKeyAlgorithmName', () => {
|
||||
});
|
||||
|
||||
describe(RoomId.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new RoomId('!foo') }).toThrow();
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new RoomId("!foo");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
const room = new RoomId('!foo:bar.org');
|
||||
const room = new RoomId("!foo:bar.org");
|
||||
|
||||
test('localpart is present', () => {
|
||||
expect(room.localpart).toStrictEqual('foo');
|
||||
test("localpart is present", () => {
|
||||
expect(room.localpart).toStrictEqual("foo");
|
||||
});
|
||||
|
||||
test('server name is present', () => {
|
||||
test("server name is present", () => {
|
||||
expect(room.serverName).toBeInstanceOf(ServerName);
|
||||
});
|
||||
|
||||
test('can read the room ID as string', () => {
|
||||
expect(room.toString()).toStrictEqual('!foo:bar.org');
|
||||
test("can read the room ID as string", () => {
|
||||
expect(room.toString()).toStrictEqual("!foo:bar.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe(ServerName.name, () => {
|
||||
test('cannot be invalid', () => {
|
||||
expect(() => { new ServerName('@foobar') }).toThrow()
|
||||
test("cannot be invalid", () => {
|
||||
expect(() => {
|
||||
new ServerName("@foobar");
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('host is present', () => {
|
||||
expect(new ServerName('foo.org').host).toStrictEqual('foo.org');
|
||||
test("host is present", () => {
|
||||
expect(new ServerName("foo.org").host).toStrictEqual("foo.org");
|
||||
});
|
||||
|
||||
test('port can be optional', () => {
|
||||
expect(new ServerName('foo.org').port).toStrictEqual(null);
|
||||
expect(new ServerName('foo.org:1234').port).toStrictEqual(1234);
|
||||
test("port can be optional", () => {
|
||||
expect(new ServerName("foo.org").port).toStrictEqual(null);
|
||||
expect(new ServerName("foo.org:1234").port).toStrictEqual(1234);
|
||||
});
|
||||
|
||||
test('server is not an IP literal', () => {
|
||||
expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false);
|
||||
test("server is not an IP literal", () => {
|
||||
expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,74 +1,120 @@
|
||||
const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs/promises');
|
||||
const {
|
||||
OlmMachine,
|
||||
UserId,
|
||||
DeviceId,
|
||||
DeviceKeyId,
|
||||
RoomId,
|
||||
DeviceLists,
|
||||
RequestType,
|
||||
KeysUploadRequest,
|
||||
KeysQueryRequest,
|
||||
KeysClaimRequest,
|
||||
EncryptionSettings,
|
||||
DecryptedRoomEvent,
|
||||
VerificationState,
|
||||
CrossSigningStatus,
|
||||
MaybeSignature,
|
||||
} = require("../");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const fs = require("fs/promises");
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
test('cannot be instantiated with the constructor', () => {
|
||||
expect(() => { new OlmMachine() }).toThrow();
|
||||
test("cannot be instantiated with the constructor", () => {
|
||||
expect(() => {
|
||||
new OlmMachine();
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('can be instantiated with the async initializer', async () => {
|
||||
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
|
||||
test("can be instantiated with the async initializer", async () => {
|
||||
expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
describe('can be instantiated with a store', () => {
|
||||
test('with no passphrase', async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
|
||||
describe("can be instantiated with a store", () => {
|
||||
test("with no passphrase", async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
|
||||
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory)).toBeInstanceOf(OlmMachine);
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), temp_directory),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
|
||||
test('with a passphrase', async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
|
||||
test("with a passphrase", async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
|
||||
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory, 'hello')).toBeInstanceOf(OlmMachine);
|
||||
expect(
|
||||
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), temp_directory, "hello"),
|
||||
).toBeInstanceOf(OlmMachine);
|
||||
});
|
||||
});
|
||||
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("foobar");
|
||||
const room = new RoomId("!baz:matrix.org");
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return OlmMachine.initialize(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user ID', async () => {
|
||||
test("can drop/close, and then re-open", async () => {
|
||||
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
||||
|
||||
let m1 = await OlmMachine.initialize(
|
||||
new UserId("@test:bar.org"),
|
||||
new DeviceId("device"),
|
||||
temp_directory,
|
||||
"hello",
|
||||
);
|
||||
m1.close();
|
||||
|
||||
let m2 = await OlmMachine.initialize(
|
||||
new UserId("@test:bar.org"),
|
||||
new DeviceId("device"),
|
||||
temp_directory,
|
||||
"hello",
|
||||
);
|
||||
m2.close();
|
||||
});
|
||||
|
||||
test("can read user ID", async () => {
|
||||
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
|
||||
});
|
||||
|
||||
test('can read device ID', async () => {
|
||||
test("can read device ID", async () => {
|
||||
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
|
||||
});
|
||||
|
||||
test('can read identity keys', async () => {
|
||||
test("can read identity keys", async () => {
|
||||
const identityKeys = (await machine()).identityKeys;
|
||||
|
||||
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
||||
});
|
||||
|
||||
test('can receive sync changes', async () => {
|
||||
test("can receive sync changes", async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = {};
|
||||
const unusedFallbackKeys = [];
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
});
|
||||
|
||||
test('can get the outgoing requests that need to be send out', async () => {
|
||||
test("can get the outgoing requests that need to be send out", async () => {
|
||||
const m = await machine();
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = {};
|
||||
const unusedFallbackKeys = [];
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
const receiveSyncChanges = JSON.parse(
|
||||
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
|
||||
);
|
||||
|
||||
expect(receiveSyncChanges).toEqual([]);
|
||||
|
||||
@@ -98,12 +144,12 @@ describe(OlmMachine.name, () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('setup workflow to mark requests as sent', () => {
|
||||
describe("setup workflow to mark requests as sent", () => {
|
||||
let m;
|
||||
let ougoingRequests;
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
|
||||
m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID"));
|
||||
|
||||
const toDeviceEvents = JSON.stringify([]);
|
||||
const changedDevices = new DeviceLists();
|
||||
@@ -116,17 +162,17 @@ describe(OlmMachine.name, () => {
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('can mark requests as sent', async () => {
|
||||
test("can mark requests as sent", async () => {
|
||||
{
|
||||
const request = outgoingRequests[0];
|
||||
expect(request).toBeInstanceOf(KeysUploadRequest);
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
one_time_key_counts: {
|
||||
curve25519: 10,
|
||||
signed_curve25519: 20,
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
@@ -138,31 +184,29 @@ describe(OlmMachine.name, () => {
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@alice:example.org": {
|
||||
"JLAFKJWSCS": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"keys": {
|
||||
JLAFKJWSCS: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "JLAFKJWSCS",
|
||||
keys: {
|
||||
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
||||
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@alice:example.org": {
|
||||
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
||||
}
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
|
||||
},
|
||||
},
|
||||
"unsigned": {
|
||||
"device_display_name": "Alice's mobile phone"
|
||||
unsigned: {
|
||||
device_display_name: "Alice's mobile phone",
|
||||
},
|
||||
"user_id": "@alice:example.org"
|
||||
}
|
||||
}
|
||||
user_id: "@alice:example.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
@@ -170,122 +214,121 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup workflow to encrypt/decrypt events', () => {
|
||||
describe("setup workflow to encrypt/decrypt events", () => {
|
||||
let m;
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('JLAFKJWSCS');
|
||||
const room = new RoomId('!test:localhost');
|
||||
const user = new UserId("@alice:example.org");
|
||||
const device = new DeviceId("JLAFKJWSCS");
|
||||
const room = new RoomId("!test:localhost");
|
||||
|
||||
beforeAll(async () => {
|
||||
m = await machine(user, device);
|
||||
});
|
||||
|
||||
test('can pass keysquery and keysclaim requests directly', async () => {
|
||||
|
||||
test("can pass keysquery and keysclaim requests directly", async () => {
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"device_keys": {
|
||||
device_keys: {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "AFGUOBTZWM",
|
||||
"keys": {
|
||||
AFGUOBTZWM: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "AFGUOBTZWM",
|
||||
keys: {
|
||||
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
|
||||
}
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA",
|
||||
},
|
||||
},
|
||||
user_id: "@example:localhost",
|
||||
unsigned: {
|
||||
device_display_name: "rust-sdk",
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "rust-sdk"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
failures: {},
|
||||
master_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["master"],
|
||||
keys: {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:TCSJXPWGVS":
|
||||
"+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"self_signing_keys": {
|
||||
self_signing_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["self_signing"],
|
||||
keys: {
|
||||
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI":
|
||||
"kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"user_signing_keys": {
|
||||
user_signing_keys: {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
|
||||
user_id: "@example:localhost",
|
||||
usage: ["user_signing"],
|
||||
keys: {
|
||||
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s":
|
||||
"g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s",
|
||||
},
|
||||
"signatures": {
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
|
||||
"nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
|
||||
}
|
||||
|
||||
{
|
||||
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_keys": {
|
||||
one_time_keys: {
|
||||
"@example:localhost": {
|
||||
"AFGUOBTZWM": {
|
||||
AFGUOBTZWM: {
|
||||
"signed_curve25519:AAAABQ": {
|
||||
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
"signatures": {
|
||||
key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
|
||||
}
|
||||
}
|
||||
}
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"failures": {}
|
||||
failures: {},
|
||||
});
|
||||
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
|
||||
const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response);
|
||||
}
|
||||
});
|
||||
|
||||
test('can share a room key', async () => {
|
||||
const other_users = [new UserId('@example:localhost')];
|
||||
test("can share a room key", async () => {
|
||||
const other_users = [new UserId("@example:localhost")];
|
||||
|
||||
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
|
||||
|
||||
@@ -293,19 +336,21 @@ describe(OlmMachine.name, () => {
|
||||
expect(requests[0].event_type).toBeDefined();
|
||||
expect(requests[0].txn_id).toBeDefined();
|
||||
expect(requests[0].messages).toBeDefined();
|
||||
expect(requests[0].messages['@example:localhost']).toBeDefined();
|
||||
expect(requests[0].messages["@example:localhost"]).toBeDefined();
|
||||
});
|
||||
|
||||
let encrypted;
|
||||
|
||||
test('can encrypt an event', async () => {
|
||||
encrypted = JSON.parse(await m.encryptRoomEvent(
|
||||
room,
|
||||
'm.room.message',
|
||||
JSON.stringify({
|
||||
"hello": "world"
|
||||
}),
|
||||
));
|
||||
test("can encrypt an event", async () => {
|
||||
encrypted = JSON.parse(
|
||||
await m.encryptRoomEvent(
|
||||
room,
|
||||
"m.room.message",
|
||||
JSON.stringify({
|
||||
hello: "world",
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(encrypted.algorithm).toBeDefined();
|
||||
expect(encrypted.ciphertext).toBeDefined();
|
||||
@@ -314,17 +359,17 @@ describe(OlmMachine.name, () => {
|
||||
expect(encrypted.session_id).toBeDefined();
|
||||
});
|
||||
|
||||
test('can decrypt an event', async () => {
|
||||
test("can decrypt an event", async () => {
|
||||
const decrypted = await m.decryptRoomEvent(
|
||||
JSON.stringify({
|
||||
"type": "m.room.encrypted",
|
||||
"event_id": "$xxxxx:example.org",
|
||||
"origin_server_ts": Date.now(),
|
||||
"sender": user.toString(),
|
||||
type: "m.room.encrypted",
|
||||
event_id: "$xxxxx:example.org",
|
||||
origin_server_ts: Date.now(),
|
||||
sender: user.toString(),
|
||||
content: encrypted,
|
||||
unsigned: {
|
||||
"age": 1234
|
||||
}
|
||||
age: 1234,
|
||||
},
|
||||
}),
|
||||
room,
|
||||
);
|
||||
@@ -343,13 +388,13 @@ describe(OlmMachine.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('can update tracked users', async () => {
|
||||
test("can update tracked users", async () => {
|
||||
const m = await machine();
|
||||
|
||||
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test('can read cross-signing status', async () => {
|
||||
test("can read cross-signing status", async () => {
|
||||
const m = await machine();
|
||||
const crossSigningStatus = await m.crossSigningStatus();
|
||||
|
||||
@@ -359,9 +404,9 @@ describe(OlmMachine.name, () => {
|
||||
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('can sign a message', async () => {
|
||||
test("can sign a message", async () => {
|
||||
const m = await machine();
|
||||
const signatures = await m.sign('foo');
|
||||
const signatures = await m.sign("foo");
|
||||
|
||||
expect(signatures.isEmpty).toStrictEqual(false);
|
||||
expect(signatures.count).toStrictEqual(1n);
|
||||
@@ -375,26 +420,26 @@ describe(OlmMachine.name, () => {
|
||||
expect(signature).toMatchObject({
|
||||
"ed25519:foobar": expect.any(MaybeSignature),
|
||||
});
|
||||
expect(signature['ed25519:foobar'].isValid).toStrictEqual(true);
|
||||
expect(signature['ed25519:foobar'].isInvalid).toStrictEqual(false);
|
||||
expect(signature['ed25519:foobar'].invalidSignatureSource).toBeNull();
|
||||
expect(signature["ed25519:foobar"].isValid).toStrictEqual(true);
|
||||
expect(signature["ed25519:foobar"].isInvalid).toStrictEqual(false);
|
||||
expect(signature["ed25519:foobar"].invalidSignatureSource).toBeNull();
|
||||
|
||||
base64 = signature['ed25519:foobar'].signature.toBase64();
|
||||
base64 = signature["ed25519:foobar"].signature.toBase64();
|
||||
|
||||
expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/);
|
||||
expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64);
|
||||
expect(signature["ed25519:foobar"].signature.ed25519.toBase64()).toStrictEqual(base64);
|
||||
}
|
||||
|
||||
// `getSignature`
|
||||
{
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
|
||||
const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar"));
|
||||
expect(signature.toBase64()).toStrictEqual(base64);
|
||||
}
|
||||
|
||||
// Unknown signatures.
|
||||
{
|
||||
expect(signatures.get(new UserId('@hello:example.org'))).toBeNull();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeNull();
|
||||
expect(signatures.get(new UserId("@hello:example.org"))).toBeNull();
|
||||
expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../');
|
||||
const {
|
||||
RequestType,
|
||||
KeysUploadRequest,
|
||||
KeysQueryRequest,
|
||||
KeysClaimRequest,
|
||||
ToDeviceRequest,
|
||||
SignatureUploadRequest,
|
||||
RoomMessageRequest,
|
||||
KeysBackupRequest,
|
||||
} = require("../");
|
||||
|
||||
describe('RequestType', () => {
|
||||
test('has the correct variant values', () => {
|
||||
describe("RequestType", () => {
|
||||
test("has the correct variant values", () => {
|
||||
expect(RequestType.KeysUpload).toStrictEqual(0);
|
||||
expect(RequestType.KeysQuery).toStrictEqual(1);
|
||||
expect(RequestType.KeysClaim).toStrictEqual(2);
|
||||
@@ -22,8 +31,10 @@ for (const request of [
|
||||
KeysBackupRequest,
|
||||
]) {
|
||||
describe(request.name, () => {
|
||||
test('cannot be instantiated', () => {
|
||||
expect(() => { new (request)() }).toThrow();
|
||||
test("cannot be instantiated", () => {
|
||||
expect(() => {
|
||||
new request();
|
||||
}).toThrow();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const { DecryptedRoomEvent } = require('../');
|
||||
const { DecryptedRoomEvent } = require("../");
|
||||
|
||||
describe(DecryptedRoomEvent.name, () => {
|
||||
test('cannot be instantiated', () => {
|
||||
expect(() => { new DecryptedRoomEvent() }).toThrow();
|
||||
test("cannot be instantiated", () => {
|
||||
expect(() => {
|
||||
new DecryptedRoomEvent();
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { DeviceLists, UserId } = require('../');
|
||||
const { DeviceLists, UserId } = require("../");
|
||||
|
||||
describe(DeviceLists.name, () => {
|
||||
test('can be empty', () => {
|
||||
test("can be empty", () => {
|
||||
const empty = new DeviceLists();
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
@@ -9,7 +9,7 @@ describe(DeviceLists.name, () => {
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('can be coerced empty', () => {
|
||||
test("can be coerced empty", () => {
|
||||
const empty = new DeviceLists([], []);
|
||||
|
||||
expect(empty.isEmpty()).toStrictEqual(true);
|
||||
@@ -17,15 +17,15 @@ describe(DeviceLists.name, () => {
|
||||
expect(empty.left).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('returns the correct `changed` and `left`', () => {
|
||||
const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]);
|
||||
test("returns the correct `changed` and `left`", () => {
|
||||
const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]);
|
||||
|
||||
expect(list.isEmpty()).toStrictEqual(false);
|
||||
|
||||
expect(list.changed).toHaveLength(1);
|
||||
expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org');
|
||||
expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org");
|
||||
|
||||
expect(list.left).toHaveLength(1);
|
||||
expect(list.left[0].toString()).toStrictEqual('@baz:qux.org');
|
||||
expect(list.left[0].toString()).toStrictEqual("@baz:qux.org");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["index.d.ts"],
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
"readme": "README.md"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" }
|
||||
matrix-sdk-indexeddb = { version = "0.2.0", path = "../matrix-sdk-indexeddb", default-features = false, optional = true }
|
||||
matrix-sdk-sled = { version = "0.2.0", path = "../matrix-sdk-sled", default-features = false, optional = true }
|
||||
mime = "0.3.16"
|
||||
pin-project-lite = "0.2.9"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
reqwest = { version = "0.11.10", default_features = false }
|
||||
ruma = { workspace = true, features = ["compat", "rand", "unstable-msc2448", "unstable-msc2965"] }
|
||||
|
||||
@@ -24,7 +24,7 @@ use tracing::error;
|
||||
use super::{
|
||||
inner::TimelineInner,
|
||||
to_device::{handle_forwarded_room_key_event, handle_room_key_event},
|
||||
Timeline,
|
||||
Timeline, TimelineEventHandlerHandles,
|
||||
};
|
||||
use crate::room;
|
||||
|
||||
@@ -102,7 +102,7 @@ impl TimelineBuilder {
|
||||
handle_forwarded_room_key_event(inner.clone(), room.room_id().to_owned()),
|
||||
);
|
||||
|
||||
let mut event_handler_handles = vec![
|
||||
let mut handles = vec![
|
||||
timeline_event_handle,
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
room_key_handle,
|
||||
@@ -135,14 +135,15 @@ impl TimelineBuilder {
|
||||
}
|
||||
}
|
||||
});
|
||||
event_handler_handles.push(fully_read_handle);
|
||||
handles.push(fully_read_handle);
|
||||
}
|
||||
|
||||
let client = room.client.clone();
|
||||
let timeline = Timeline {
|
||||
inner,
|
||||
start_token: Mutex::new(prev_token),
|
||||
_end_token: Mutex::new(None),
|
||||
event_handler_handles,
|
||||
event_handler_handles: Arc::new(TimelineEventHandlerHandles { client, handles }),
|
||||
};
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
|
||||
@@ -259,6 +259,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Handling event");
|
||||
|
||||
match event_kind {
|
||||
TimelineEventKind::Message { content } => match content {
|
||||
AnyMessageLikeEventContent::Reaction(c) => {
|
||||
@@ -312,9 +314,11 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
}
|
||||
|
||||
if !self.result.item_added {
|
||||
trace!("No new item added");
|
||||
if let Flow::Remote { position: TimelineItemPosition::Update(idx), .. } = self.flow {
|
||||
// If add was not called, that means the UTD event is one that
|
||||
// wouldn't normally be visible. Remove it.
|
||||
trace!("Removing UTD that was successfully retried");
|
||||
self.timeline_items.remove(idx);
|
||||
}
|
||||
|
||||
@@ -562,6 +566,8 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
|
||||
match &self.flow {
|
||||
Flow::Local { timestamp, .. } => {
|
||||
trace!("Adding new local timeline item");
|
||||
|
||||
// Check if the latest event has the same date as this event.
|
||||
if let Some(latest_event) =
|
||||
self.timeline_items.iter().rev().find_map(|item| item.as_event())
|
||||
@@ -571,10 +577,12 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
if let Some(day_divider_item) =
|
||||
maybe_create_day_divider_from_timestamps(old_ts, *timestamp)
|
||||
{
|
||||
trace!("Adding day divider");
|
||||
self.timeline_items.push_cloned(Arc::new(day_divider_item));
|
||||
}
|
||||
} else {
|
||||
// If there is no event item, there is no day divider yet.
|
||||
trace!("Adding first day divider");
|
||||
self.timeline_items
|
||||
.push_cloned(Arc::new(TimelineItem::day_divider(*timestamp)));
|
||||
}
|
||||
@@ -582,7 +590,24 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
self.timeline_items.push_cloned(item);
|
||||
}
|
||||
|
||||
Flow::Remote { position: TimelineItemPosition::Start, origin_server_ts, .. } => {
|
||||
Flow::Remote {
|
||||
position: TimelineItemPosition::Start,
|
||||
event_id,
|
||||
origin_server_ts,
|
||||
..
|
||||
} => {
|
||||
if self
|
||||
.timeline_items
|
||||
.iter()
|
||||
.filter_map(|ev| ev.as_event()?.event_id())
|
||||
.any(|id| id == event_id)
|
||||
{
|
||||
trace!("Skipping back-paginated event that has already been seen");
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("Adding new remote timeline item at the start");
|
||||
|
||||
// If there is a loading indicator at the top, check for / insert the day
|
||||
// divider at position 1 and the new event at 2 rather than 0 and 1.
|
||||
let offset = match self.timeline_items.first().and_then(|item| item.as_virtual()) {
|
||||
@@ -626,17 +651,10 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
|
||||
if let Some((idx, old_item)) = result {
|
||||
if let EventTimelineItem::Remote(old_item) = old_item {
|
||||
// Item was previously received by the server. Until we
|
||||
// implement forwards pagination, this indicates a bug
|
||||
// somewhere.
|
||||
warn!(?item, ?old_item, "Received duplicate event");
|
||||
|
||||
// With /messages and /sync sometimes disagreeing on
|
||||
// order of messages, we might want to change the
|
||||
// position in some circumstances, but for now this
|
||||
// should be good enough.
|
||||
self.timeline_items.set_cloned(idx, item);
|
||||
return;
|
||||
// Item was previously received from the server. This
|
||||
// should be very rare normally, but with the sliding-
|
||||
// sync proxy, it is actually very common.
|
||||
trace!(?item, ?old_item, "Received duplicate event");
|
||||
};
|
||||
|
||||
if txn_id.is_none() {
|
||||
@@ -657,11 +675,13 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
{
|
||||
// If the old item is the last one and no day divider
|
||||
// changes need to happen, replace and return early.
|
||||
trace!(idx, "Replacing existing event");
|
||||
self.timeline_items.set_cloned(idx, item);
|
||||
return;
|
||||
} else {
|
||||
// In more complex cases, remove the item and day
|
||||
// divider (if necessary) before re-adding the item.
|
||||
trace!("Removing local echo or duplicate timeline item");
|
||||
self.timeline_items.remove(idx);
|
||||
|
||||
assert_ne!(
|
||||
@@ -673,14 +693,15 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
// Pre-requisites for removing the day divider:
|
||||
// 1. there is one preceding the old item at all
|
||||
if self.timeline_items[idx - 1].is_day_divider()
|
||||
// 2. the next item after the old one being removed
|
||||
// 2. the item after the old one that was removed
|
||||
// is virtual (it should be impossible for this
|
||||
// to be a read marker)
|
||||
&& self
|
||||
.timeline_items
|
||||
.get(idx + 1)
|
||||
.get(idx)
|
||||
.map_or(true, |item| item.is_virtual())
|
||||
{
|
||||
trace!("Removing day divider");
|
||||
self.timeline_items.remove(idx - 1);
|
||||
}
|
||||
|
||||
@@ -703,14 +724,17 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
if let Some(day_divider_item) =
|
||||
maybe_create_day_divider_from_timestamps(old_ts, *origin_server_ts)
|
||||
{
|
||||
trace!("Adding day divider");
|
||||
self.timeline_items.push_cloned(Arc::new(day_divider_item));
|
||||
}
|
||||
} else {
|
||||
// If there is not event item, there is no day divider yet.
|
||||
// If there is no event item, there is no day divider yet.
|
||||
trace!("Adding first day divider");
|
||||
self.timeline_items
|
||||
.push_cloned(Arc::new(TimelineItem::day_divider(*origin_server_ts)));
|
||||
}
|
||||
|
||||
trace!("Adding new remote timeline item at the end");
|
||||
self.timeline_items.push_cloned(item);
|
||||
}
|
||||
|
||||
@@ -763,6 +787,8 @@ pub(crate) fn update_read_marker(
|
||||
fully_read_event_in_timeline: &mut bool,
|
||||
) {
|
||||
let Some(fully_read_event) = fully_read_event else { return };
|
||||
trace!(?fully_read_event, "Updating read marker");
|
||||
|
||||
let read_marker_idx = find_read_marker(items_lock);
|
||||
let fully_read_event_idx = rfind_event_by_id(items_lock, fully_read_event).map(|(idx, _)| idx);
|
||||
|
||||
@@ -796,7 +822,9 @@ fn _update_timeline_item(
|
||||
update: impl FnOnce(&EventTimelineItem) -> Option<EventTimelineItem>,
|
||||
) {
|
||||
if let Some((idx, item)) = rfind_event_by_id(timeline_items, event_id) {
|
||||
trace!("Found timeline item to update");
|
||||
if let Some(new_item) = update(item) {
|
||||
trace!("Updating item");
|
||||
timeline_items.set_cloned(idx, Arc::new(TimelineItem::Event(new_item)));
|
||||
*items_updated += 1;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_signals::signal_vec::{MutableVec, MutableVecLockRef, SignalVec};
|
||||
use futures_signals::signal_vec::{MutableSignalVec, MutableVec, MutableVecLockRef};
|
||||
use indexmap::IndexSet;
|
||||
use matrix_sdk_base::{
|
||||
crypto::OlmMachine,
|
||||
@@ -83,7 +83,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
self.items.lock_ref()
|
||||
}
|
||||
|
||||
pub(super) fn items_signal(&self) -> impl SignalVec<Item = Arc<TimelineItem>> {
|
||||
pub(super) fn items_signal(&self) -> MutableSignalVec<Arc<TimelineItem>> {
|
||||
trace!("Creating timeline items signal");
|
||||
self.items.signal_vec_cloned()
|
||||
}
|
||||
|
||||
@@ -111,6 +112,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
|
||||
#[cfg(feature = "experimental-sliding-sync")]
|
||||
pub(super) async fn clear(&self) {
|
||||
trace!("Clearing timeline");
|
||||
|
||||
let mut timeline_meta = self.metadata.lock().await;
|
||||
let mut timeline_items = self.items.lock_mut();
|
||||
|
||||
@@ -121,6 +124,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
timeline_items.clear();
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub(super) async fn handle_live_event(
|
||||
&self,
|
||||
raw: Raw<AnySyncTimelineEvent>,
|
||||
@@ -139,6 +143,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
}
|
||||
|
||||
/// Handle the creation of a new local event.
|
||||
#[instrument(skip_all)]
|
||||
pub(super) async fn handle_local_event(
|
||||
&self,
|
||||
txn_id: OwnedTransactionId,
|
||||
@@ -167,6 +172,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
/// Update the send state of a local event represented by a transaction ID.
|
||||
///
|
||||
/// If no local event is found, a warning is raised.
|
||||
#[instrument(skip_all, fields(txn_id))]
|
||||
pub(super) fn update_event_send_state(
|
||||
&self,
|
||||
txn_id: &TransactionId,
|
||||
@@ -187,13 +193,13 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
|
||||
let Some((idx, item)) = result else {
|
||||
// Event isn't found at all.
|
||||
warn!(?txn_id, "Timeline item not found, can't add event ID");
|
||||
warn!("Timeline item not found, can't add event ID");
|
||||
return;
|
||||
};
|
||||
|
||||
let EventTimelineItem::Local(item) = item else {
|
||||
// Remote echo already received. This is very unlikely.
|
||||
trace!(?txn_id, "Remote echo received before send-event response");
|
||||
trace!("Remote echo received before send-event response");
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -201,7 +207,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
// emit an error but also override to the given sent state.
|
||||
if let EventSendState::Sent { event_id: existing_event_id } = &item.send_state {
|
||||
let new_event_id = new_event_id.map(debug);
|
||||
error!(?existing_event_id, ?new_event_id, ?txn_id, "Local echo already marked as sent");
|
||||
error!(?existing_event_id, ?new_event_id, "Local echo already marked as sent");
|
||||
}
|
||||
|
||||
let new_item = TimelineItem::Event(item.with_send_state(send_state).into());
|
||||
@@ -211,6 +217,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
/// Handle a back-paginated event.
|
||||
///
|
||||
/// Returns the number of timeline updates that were made.
|
||||
#[instrument(skip_all)]
|
||||
pub(super) async fn handle_back_paginated_event(
|
||||
&self,
|
||||
event: TimelineEvent,
|
||||
@@ -261,6 +268,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub(super) async fn handle_fully_read(&self, raw: Raw<FullyReadEvent>) {
|
||||
let fully_read_event_id = match raw.deserialize() {
|
||||
Ok(ev) => ev.content.event_id,
|
||||
@@ -273,6 +281,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
self.set_fully_read_event(fully_read_event_id).await;
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub(super) async fn set_fully_read_event(&self, fully_read_event_id: OwnedEventId) {
|
||||
let mut metadata_lock = self.metadata.lock().await;
|
||||
|
||||
@@ -413,6 +422,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
}
|
||||
|
||||
pub(super) async fn update_sender_profiles(&self) {
|
||||
trace!("Updating sender profiles");
|
||||
|
||||
// Can't lock the timeline items across .await points without making the
|
||||
// resulting future `!Send`. As a (brittle) hack around that, lock the
|
||||
// timeline items in each loop iteration but keep a lock of the metadata
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
//!
|
||||
//! See [`Timeline`] for details.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{pin::Pin, sync::Arc, task::Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
#[cfg(feature = "testing")]
|
||||
use futures_signals::signal_vec::MutableVecLockRef;
|
||||
use futures_signals::signal_vec::{SignalVec, SignalVecExt, VecDiff};
|
||||
use futures_signals::signal_vec::{MutableSignalVec, SignalVec, SignalVecExt, VecDiff};
|
||||
use matrix_sdk_base::locks::Mutex;
|
||||
use pin_project_lite::pin_project;
|
||||
use ruma::{
|
||||
assign, events::AnyMessageLikeEventContent, EventId, MilliSecondsSinceUnixEpoch, TransactionId,
|
||||
};
|
||||
@@ -33,7 +34,7 @@ use super::Joined;
|
||||
use crate::{
|
||||
event_handler::EventHandlerHandle,
|
||||
room::{self, MessagesOptions},
|
||||
Result,
|
||||
Client, Result,
|
||||
};
|
||||
|
||||
mod builder;
|
||||
@@ -70,15 +71,7 @@ pub struct Timeline {
|
||||
inner: Arc<TimelineInner<room::Common>>,
|
||||
start_token: Mutex<Option<String>>,
|
||||
_end_token: Mutex<Option<String>>,
|
||||
event_handler_handles: Vec<EventHandlerHandle>,
|
||||
}
|
||||
|
||||
impl Drop for Timeline {
|
||||
fn drop(&mut self) {
|
||||
for handle in self.event_handler_handles.drain(..) {
|
||||
self.inner.room().client().remove_event_handler(handle);
|
||||
}
|
||||
}
|
||||
event_handler_handles: Arc<TimelineEventHandlerHandles>,
|
||||
}
|
||||
|
||||
impl Timeline {
|
||||
@@ -245,7 +238,7 @@ impl Timeline {
|
||||
/// See [`SignalVecExt`](futures_signals::signal_vec::SignalVecExt) for a
|
||||
/// high-level API on top of [`SignalVec`].
|
||||
pub fn signal(&self) -> impl SignalVec<Item = Arc<TimelineItem>> {
|
||||
self.inner.items_signal()
|
||||
TimelineSignal::new(self.inner.items_signal(), self.event_handler_handles.clone())
|
||||
}
|
||||
|
||||
/// Get a stream of timeline changes.
|
||||
@@ -353,6 +346,48 @@ impl Timeline {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TimelineEventHandlerHandles {
|
||||
client: Client,
|
||||
handles: Vec<EventHandlerHandle>,
|
||||
}
|
||||
|
||||
impl Drop for TimelineEventHandlerHandles {
|
||||
fn drop(&mut self) {
|
||||
for handle in self.handles.drain(..) {
|
||||
self.client.remove_event_handler(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
struct TimelineSignal {
|
||||
#[pin]
|
||||
inner: MutableSignalVec<Arc<TimelineItem>>,
|
||||
event_handler_handles: Arc<TimelineEventHandlerHandles>,
|
||||
}
|
||||
}
|
||||
|
||||
impl TimelineSignal {
|
||||
fn new(
|
||||
inner: MutableSignalVec<Arc<TimelineItem>>,
|
||||
event_handler_handles: Arc<TimelineEventHandlerHandles>,
|
||||
) -> Self {
|
||||
Self { inner, event_handler_handles }
|
||||
}
|
||||
}
|
||||
|
||||
impl SignalVec for TimelineSignal {
|
||||
type Item = Arc<TimelineItem>;
|
||||
|
||||
fn poll_vec_change(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Option<VecDiff<Self::Item>>> {
|
||||
self.project().inner.poll_vec_change(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// A single entry in timeline.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use assert_matches::assert_matches;
|
||||
use futures_signals::signal_vec::VecDiff;
|
||||
use futures_util::StreamExt;
|
||||
use matrix_sdk_base::deserialized_responses::SyncTimelineEvent;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
assign,
|
||||
@@ -18,21 +19,34 @@ use ruma::{
|
||||
FullStateEventContent,
|
||||
},
|
||||
};
|
||||
use serde_json::json;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
use super::{TestTimeline, ALICE, BOB};
|
||||
use crate::room::timeline::{
|
||||
event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineItemContent,
|
||||
event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineItem, TimelineItemContent,
|
||||
VirtualTimelineItem,
|
||||
};
|
||||
|
||||
fn sync_timeline_event(event: JsonValue) -> SyncTimelineEvent {
|
||||
let event = serde_json::from_value(event).unwrap();
|
||||
SyncTimelineEvent { event, encryption_info: None }
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn initial_events() {
|
||||
let timeline = TestTimeline::with_initial_events([
|
||||
(*ALICE, RoomMessageEventContent::text_plain("A").into()),
|
||||
(*BOB, RoomMessageEventContent::text_plain("B").into()),
|
||||
])
|
||||
.await;
|
||||
let mut timeline = TestTimeline::new();
|
||||
timeline
|
||||
.inner
|
||||
.add_initial_events(vec![
|
||||
sync_timeline_event(
|
||||
timeline.make_message_event(*ALICE, RoomMessageEventContent::text_plain("A")),
|
||||
),
|
||||
sync_timeline_event(
|
||||
timeline.make_message_event(*BOB, RoomMessageEventContent::text_plain("B")),
|
||||
),
|
||||
])
|
||||
.await;
|
||||
|
||||
let mut stream = timeline.stream();
|
||||
|
||||
let items = assert_matches!(stream.next().await, Some(VecDiff::Replace { values }) => values);
|
||||
@@ -227,3 +241,36 @@ async fn other_state() {
|
||||
assert_matches!(ev.content(), AnyOtherFullStateEventContent::RoomTopic(c) => c);
|
||||
assert_matches!(full_content, FullStateEventContent::Redacted(_));
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn dedup_pagination() {
|
||||
let timeline = TestTimeline::new();
|
||||
|
||||
let event = timeline.make_message_event(*ALICE, RoomMessageEventContent::text_plain("o/"));
|
||||
timeline.handle_live_custom_event(event.clone()).await;
|
||||
timeline.handle_back_paginated_custom_event(event).await;
|
||||
|
||||
let timeline_items = timeline.inner.items();
|
||||
assert_eq!(timeline_items.len(), 2);
|
||||
assert_matches!(*timeline_items[0], TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_)));
|
||||
assert_matches!(*timeline_items[1], TimelineItem::Event(_));
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn dedup_initial() {
|
||||
let mut timeline = TestTimeline::new();
|
||||
|
||||
let event_a = sync_timeline_event(
|
||||
timeline.make_message_event(*ALICE, RoomMessageEventContent::text_plain("A")),
|
||||
);
|
||||
let event_b = sync_timeline_event(
|
||||
timeline.make_message_event(*BOB, RoomMessageEventContent::text_plain("B")),
|
||||
);
|
||||
|
||||
timeline.inner.add_initial_events(vec![event_a.clone(), event_b, event_a]).await;
|
||||
|
||||
let timeline_items = dbg!(timeline.inner.items());
|
||||
assert_eq!(timeline_items.len(), 3);
|
||||
assert_eq!(timeline_items[1].as_event().unwrap().sender(), *BOB);
|
||||
assert_eq!(timeline_items[2].as_event().unwrap().sender(), *ALICE);
|
||||
}
|
||||
|
||||
@@ -136,8 +136,11 @@ async fn remote_echo_new_position() {
|
||||
|
||||
// … the local echo should be removed
|
||||
assert_matches!(stream.next().await, Some(VecDiff::RemoveAt { index: 1 }));
|
||||
// … along with its day divider
|
||||
assert_matches!(stream.next().await, Some(VecDiff::RemoveAt { index: 0 }));
|
||||
|
||||
// … and the remote echo added
|
||||
// … and the remote echo added (no new day divider because both bob's and
|
||||
// alice's message are from the same day according to server timestamps)
|
||||
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
assert_matches!(item.as_event().unwrap(), EventTimelineItem::Remote(_));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use std::sync::{
|
||||
use async_trait::async_trait;
|
||||
use futures_core::Stream;
|
||||
use futures_signals::signal_vec::{SignalVecExt, VecDiff};
|
||||
use matrix_sdk_base::deserialized_responses::SyncTimelineEvent;
|
||||
use matrix_sdk_base::deserialized_responses::TimelineEvent;
|
||||
use once_cell::sync::Lazy;
|
||||
use ruma::{
|
||||
events::{
|
||||
@@ -57,29 +57,6 @@ impl TestTimeline {
|
||||
Self { inner: TimelineInner::new(TestProfileProvider), next_ts: AtomicU64::new(0) }
|
||||
}
|
||||
|
||||
async fn with_initial_events<'a>(
|
||||
events: impl IntoIterator<Item = (&'a UserId, AnyMessageLikeEventContent)>,
|
||||
) -> Self {
|
||||
let mut this =
|
||||
Self { inner: TimelineInner::new(TestProfileProvider), next_ts: AtomicU64::new(0) };
|
||||
|
||||
this.inner
|
||||
.add_initial_events(
|
||||
events
|
||||
.into_iter()
|
||||
.map(|(sender, content)| {
|
||||
let event =
|
||||
serde_json::from_value(this.make_message_event(sender, content))
|
||||
.unwrap();
|
||||
SyncTimelineEvent { event, encryption_info: None }
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.await;
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn stream(&self) -> impl Stream<Item = VecDiff<Arc<TimelineItem>>> {
|
||||
self.inner.items_signal().to_stream()
|
||||
}
|
||||
@@ -171,6 +148,12 @@ impl TestTimeline {
|
||||
txn_id
|
||||
}
|
||||
|
||||
async fn handle_back_paginated_custom_event(&self, event: JsonValue) {
|
||||
let timeline_event =
|
||||
TimelineEvent { event: Raw::new(&event).unwrap().cast(), encryption_info: None };
|
||||
self.inner.handle_back_paginated_event(timeline_event).await;
|
||||
}
|
||||
|
||||
/// Set the next server timestamp.
|
||||
///
|
||||
/// Timestamps will continue to increase by 1 (millisecond) from that value.
|
||||
|
||||
@@ -29,6 +29,10 @@ services:
|
||||
|
||||
sliding-sync-proxy:
|
||||
image: ghcr.io/matrix-org/sliding-sync:v0.99.0
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
links:
|
||||
- synapse
|
||||
- postgres
|
||||
|
||||
Reference in New Issue
Block a user