Merge branch 'main' into test-sliding-sync-timeline-limit-duplication

This commit is contained in:
Ivan Enderlin
2023-02-16 08:45:22 +01:00
committed by GitHub
46 changed files with 1576 additions and 1110 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -2705,6 +2705,7 @@ dependencies = [
"matrix-sdk-test",
"mime",
"once_cell",
"pin-project-lite",
"rand 0.8.5",
"reqwest",
"ruma",

View File

@@ -0,0 +1 @@
/pkg

View 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",
};

View File

@@ -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

View File

@@ -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 .",

View File

@@ -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;
}

View File

@@ -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),

View File

@@ -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/);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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");
});
});
})
});

View File

@@ -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(),
}
},
});
})
});
});
});

View File

@@ -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);

View File

@@ -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");
});
});

View File

@@ -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();
});
}
});

View File

@@ -5,6 +5,6 @@
"typedocOptions": {
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
"out": "docs",
"readme": "README.md",
"readme": "README.md"
}
}

View File

@@ -0,0 +1 @@
/pkg

View 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",
};

View File

@@ -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

View File

@@ -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

View File

@@ -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}`);
}

View File

@@ -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",

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
});
});

View File

@@ -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();
}
});
});

View File

@@ -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();
});
})
});
}

View File

@@ -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();
});
});

View File

@@ -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");
});
});

View File

@@ -5,6 +5,6 @@
"typedocOptions": {
"entryPoints": ["index.d.ts"],
"out": "docs",
"readme": "README.md",
"readme": "README.md"
}
}

View File

@@ -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"] }

View File

@@ -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")]

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)]

View File

@@ -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);
}

View File

@@ -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(_));
}

View File

@@ -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.

View File

@@ -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