You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Element-R: implement remaining OutgoingMessage request types (#3083)
This is a follow-up to #3019: it implements the remaining two types of message types, now that rust SDK has sensibly-shaped types for them.
This commit is contained in:
committed by
GitHub
parent
1c26dc0233
commit
0c1d5f6b25
@@ -55,7 +55,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.2",
|
"@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.3",
|
||||||
"another-json": "^0.2.0",
|
"another-json": "^0.2.0",
|
||||||
"bs58": "^5.0.0",
|
"bs58": "^5.0.0",
|
||||||
"content-type": "^1.0.4",
|
"content-type": "^1.0.4",
|
||||||
|
180
spec/unit/rust-crypto/OutgoingRequestProcessor.spec.ts
Normal file
180
spec/unit/rust-crypto/OutgoingRequestProcessor.spec.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import MockHttpBackend from "matrix-mock-request";
|
||||||
|
import { Mocked } from "jest-mock";
|
||||||
|
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
|
||||||
|
import {
|
||||||
|
KeysBackupRequest,
|
||||||
|
KeysClaimRequest,
|
||||||
|
KeysQueryRequest,
|
||||||
|
KeysUploadRequest,
|
||||||
|
RoomMessageRequest,
|
||||||
|
SignatureUploadRequest,
|
||||||
|
ToDeviceRequest,
|
||||||
|
} from "@matrix-org/matrix-sdk-crypto-js";
|
||||||
|
|
||||||
|
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
|
||||||
|
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi } from "../../../src";
|
||||||
|
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||||
|
|
||||||
|
describe("OutgoingRequestProcessor", () => {
|
||||||
|
/** the OutgoingRequestProcessor implementation under test */
|
||||||
|
let processor: OutgoingRequestProcessor;
|
||||||
|
|
||||||
|
/** A mock http backend which processor is connected to */
|
||||||
|
let httpBackend: MockHttpBackend;
|
||||||
|
|
||||||
|
/** a mocked-up OlmMachine which processor is connected to */
|
||||||
|
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||||
|
|
||||||
|
/** wait for a call to olmMachine.markRequestAsSent */
|
||||||
|
function awaitCallToMarkAsSent(): Promise<void> {
|
||||||
|
return new Promise((resolve, _reject) => {
|
||||||
|
olmMachine.markRequestAsSent.mockImplementationOnce(async () => {
|
||||||
|
resolve(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
httpBackend = new MockHttpBackend();
|
||||||
|
|
||||||
|
const dummyEventEmitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
|
||||||
|
const httpApi = new MatrixHttpApi(dummyEventEmitter, {
|
||||||
|
baseUrl: "https://example.com",
|
||||||
|
prefix: "/_matrix",
|
||||||
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
|
onlyData: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
olmMachine = {
|
||||||
|
markRequestAsSent: jest.fn(),
|
||||||
|
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||||
|
|
||||||
|
processor = new OutgoingRequestProcessor(olmMachine, httpApi);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* simple requests that map directly to the request body */
|
||||||
|
const tests: Array<[string, any, "POST" | "PUT", string]> = [
|
||||||
|
["KeysUploadRequest", KeysUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/upload"],
|
||||||
|
["KeysQueryRequest", KeysQueryRequest, "POST", "https://example.com/_matrix/client/v3/keys/query"],
|
||||||
|
["KeysClaimRequest", KeysClaimRequest, "POST", "https://example.com/_matrix/client/v3/keys/claim"],
|
||||||
|
[
|
||||||
|
"SignatureUploadRequest",
|
||||||
|
SignatureUploadRequest,
|
||||||
|
"POST",
|
||||||
|
"https://example.com/_matrix/client/v3/keys/signatures/upload",
|
||||||
|
],
|
||||||
|
["KeysBackupRequest", KeysBackupRequest, "PUT", "https://example.com/_matrix/client/v3/room_keys/keys"],
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(tests)(`should handle %ss`, async (_, RequestClass, expectedMethod, expectedPath) => {
|
||||||
|
// first, mock up a request as we might expect to receive it from the Rust layer ...
|
||||||
|
const testBody = '{ "foo": "bar" }';
|
||||||
|
const outgoingRequest = new RequestClass("1234", testBody);
|
||||||
|
|
||||||
|
// ... then poke it into the OutgoingRequestProcessor under test.
|
||||||
|
const reqProm = processor.makeOutgoingRequest(outgoingRequest);
|
||||||
|
|
||||||
|
// Now: check that it makes a matching HTTP request ...
|
||||||
|
const testResponse = '{ "result": 1 }';
|
||||||
|
httpBackend
|
||||||
|
.when(expectedMethod, "/_matrix")
|
||||||
|
.check((req) => {
|
||||||
|
expect(req.path).toEqual(expectedPath);
|
||||||
|
expect(req.rawData).toEqual(testBody);
|
||||||
|
expect(req.headers["Accept"]).toEqual("application/json");
|
||||||
|
expect(req.headers["Content-Type"]).toEqual("application/json");
|
||||||
|
})
|
||||||
|
.respond(200, testResponse, true);
|
||||||
|
|
||||||
|
// ... and that it calls OlmMachine.markAsSent.
|
||||||
|
const markSentCallPromise = awaitCallToMarkAsSent();
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
|
||||||
|
await Promise.all([reqProm, markSentCallPromise]);
|
||||||
|
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse);
|
||||||
|
httpBackend.verifyNoOutstandingRequests();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle ToDeviceRequests", async () => {
|
||||||
|
// first, mock up the ToDeviceRequest as we might expect to receive it from the Rust layer ...
|
||||||
|
const testBody = '{ "foo": "bar" }';
|
||||||
|
const outgoingRequest = new ToDeviceRequest("1234", "test/type", "test/txnid", testBody);
|
||||||
|
|
||||||
|
// ... then poke it into the OutgoingRequestProcessor under test.
|
||||||
|
const reqProm = processor.makeOutgoingRequest(outgoingRequest);
|
||||||
|
|
||||||
|
// Now: check that it makes a matching HTTP request ...
|
||||||
|
const testResponse = '{ "result": 1 }';
|
||||||
|
httpBackend
|
||||||
|
.when("PUT", "/_matrix")
|
||||||
|
.check((req) => {
|
||||||
|
expect(req.path).toEqual("https://example.com/_matrix/client/v3/sendToDevice/test%2Ftype/test%2Ftxnid");
|
||||||
|
expect(req.rawData).toEqual(testBody);
|
||||||
|
expect(req.headers["Accept"]).toEqual("application/json");
|
||||||
|
expect(req.headers["Content-Type"]).toEqual("application/json");
|
||||||
|
})
|
||||||
|
.respond(200, testResponse, true);
|
||||||
|
|
||||||
|
// ... and that it calls OlmMachine.markAsSent.
|
||||||
|
const markSentCallPromise = awaitCallToMarkAsSent();
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
|
||||||
|
await Promise.all([reqProm, markSentCallPromise]);
|
||||||
|
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse);
|
||||||
|
httpBackend.verifyNoOutstandingRequests();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle RoomMessageRequests", async () => {
|
||||||
|
// first, mock up the RoomMessageRequest as we might expect to receive it from the Rust layer ...
|
||||||
|
const testBody = '{ "foo": "bar" }';
|
||||||
|
const outgoingRequest = new RoomMessageRequest("1234", "test/room", "test/txnid", "test/type", testBody);
|
||||||
|
|
||||||
|
// ... then poke it into the OutgoingRequestProcessor under test.
|
||||||
|
const reqProm = processor.makeOutgoingRequest(outgoingRequest);
|
||||||
|
|
||||||
|
// Now: check that it makes a matching HTTP request ...
|
||||||
|
const testResponse = '{ "result": 1 }';
|
||||||
|
httpBackend
|
||||||
|
.when("PUT", "/_matrix")
|
||||||
|
.check((req) => {
|
||||||
|
expect(req.path).toEqual(
|
||||||
|
"https://example.com/_matrix/client/v3/room/test%2Froom/send/test%2Ftype/test%2Ftxnid",
|
||||||
|
);
|
||||||
|
expect(req.rawData).toEqual(testBody);
|
||||||
|
expect(req.headers["Accept"]).toEqual("application/json");
|
||||||
|
expect(req.headers["Content-Type"]).toEqual("application/json");
|
||||||
|
})
|
||||||
|
.respond(200, testResponse, true);
|
||||||
|
|
||||||
|
// ... and that it calls OlmMachine.markAsSent.
|
||||||
|
const markSentCallPromise = awaitCallToMarkAsSent();
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
|
||||||
|
await Promise.all([reqProm, markSentCallPromise]);
|
||||||
|
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse);
|
||||||
|
httpBackend.verifyNoOutstandingRequests();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not explode with unknown requests", async () => {
|
||||||
|
const outgoingRequest = { id: "5678", type: 987 };
|
||||||
|
const markSentCallPromise = awaitCallToMarkAsSent();
|
||||||
|
await Promise.all([processor.makeOutgoingRequest(outgoingRequest), markSentCallPromise]);
|
||||||
|
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("5678", 987, "");
|
||||||
|
});
|
||||||
|
});
|
@@ -17,24 +17,16 @@ limitations under the License.
|
|||||||
import "fake-indexeddb/auto";
|
import "fake-indexeddb/auto";
|
||||||
import { IDBFactory } from "fake-indexeddb";
|
import { IDBFactory } from "fake-indexeddb";
|
||||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
|
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
|
||||||
import {
|
import { KeysQueryRequest, OlmMachine } from "@matrix-org/matrix-sdk-crypto-js";
|
||||||
KeysBackupRequest,
|
|
||||||
KeysClaimRequest,
|
|
||||||
KeysQueryRequest,
|
|
||||||
KeysUploadRequest,
|
|
||||||
OlmMachine,
|
|
||||||
SignatureUploadRequest,
|
|
||||||
} from "@matrix-org/matrix-sdk-crypto-js";
|
|
||||||
import { Mocked } from "jest-mock";
|
import { Mocked } from "jest-mock";
|
||||||
import MockHttpBackend from "matrix-mock-request";
|
|
||||||
|
|
||||||
import { RustCrypto } from "../../src/rust-crypto/rust-crypto";
|
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
|
||||||
import { initRustCrypto } from "../../src/rust-crypto";
|
import { initRustCrypto } from "../../../src/rust-crypto";
|
||||||
import { HttpApiEvent, HttpApiEventHandlerMap, IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../src";
|
import { IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src";
|
||||||
import { TypedEventEmitter } from "../../src/models/typed-event-emitter";
|
import { mkEvent } from "../../test-utils/test-utils";
|
||||||
import { mkEvent } from "../test-utils/test-utils";
|
import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend";
|
||||||
import { CryptoBackend } from "../../src/common-crypto/CryptoBackend";
|
import { IEventDecryptionResult } from "../../../src/@types/crypto";
|
||||||
import { IEventDecryptionResult } from "../../src/@types/crypto";
|
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
||||||
@@ -106,8 +98,8 @@ describe("RustCrypto", () => {
|
|||||||
/** the RustCrypto implementation under test */
|
/** the RustCrypto implementation under test */
|
||||||
let rustCrypto: RustCrypto;
|
let rustCrypto: RustCrypto;
|
||||||
|
|
||||||
/** A mock http backend which rustCrypto is connected to */
|
/** A mock OutgoingRequestProcessor which rustCrypto is connected to */
|
||||||
let httpBackend: MockHttpBackend;
|
let outgoingRequestProcessor: Mocked<OutgoingRequestProcessor>;
|
||||||
|
|
||||||
/** a mocked-up OlmMachine which rustCrypto is connected to */
|
/** a mocked-up OlmMachine which rustCrypto is connected to */
|
||||||
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
|
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||||
@@ -116,28 +108,25 @@ describe("RustCrypto", () => {
|
|||||||
* the front of the queue, until it is empty. */
|
* the front of the queue, until it is empty. */
|
||||||
let outgoingRequestQueue: Array<Array<any>>;
|
let outgoingRequestQueue: Array<Array<any>>;
|
||||||
|
|
||||||
/** wait for a call to olmMachine.markRequestAsSent */
|
/** wait for a call to outgoingRequestProcessor.makeOutgoingRequest.
|
||||||
function awaitCallToMarkAsSent(): Promise<void> {
|
*
|
||||||
return new Promise((resolve, _reject) => {
|
* The promise resolves to a callback: the makeOutgoingRequest call will not complete until the returned
|
||||||
olmMachine.markRequestAsSent.mockImplementationOnce(async () => {
|
* callback is called.
|
||||||
resolve(undefined);
|
*/
|
||||||
|
function awaitCallToMakeOutgoingRequest(): Promise<() => void> {
|
||||||
|
return new Promise<() => void>((resolveCalledPromise, _reject) => {
|
||||||
|
outgoingRequestProcessor.makeOutgoingRequest.mockImplementationOnce(async () => {
|
||||||
|
const completePromise = new Promise<void>((resolveCompletePromise, _reject) => {
|
||||||
|
resolveCalledPromise(resolveCompletePromise);
|
||||||
|
});
|
||||||
|
return completePromise;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
httpBackend = new MockHttpBackend();
|
|
||||||
|
|
||||||
await RustSdkCryptoJs.initAsync();
|
await RustSdkCryptoJs.initAsync();
|
||||||
|
|
||||||
const dummyEventEmitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
|
|
||||||
const httpApi = new MatrixHttpApi(dummyEventEmitter, {
|
|
||||||
baseUrl: "https://example.com",
|
|
||||||
prefix: "/_matrix",
|
|
||||||
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
|
||||||
onlyData: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// for these tests we use a mock OlmMachine, with an implementation of outgoingRequests that
|
// for these tests we use a mock OlmMachine, with an implementation of outgoingRequests that
|
||||||
// returns objects from outgoingRequestQueue
|
// returns objects from outgoingRequestQueue
|
||||||
outgoingRequestQueue = [];
|
outgoingRequestQueue = [];
|
||||||
@@ -145,91 +134,55 @@ describe("RustCrypto", () => {
|
|||||||
outgoingRequests: jest.fn().mockImplementation(() => {
|
outgoingRequests: jest.fn().mockImplementation(() => {
|
||||||
return Promise.resolve(outgoingRequestQueue.shift() ?? []);
|
return Promise.resolve(outgoingRequestQueue.shift() ?? []);
|
||||||
}),
|
}),
|
||||||
markRequestAsSent: jest.fn(),
|
|
||||||
close: jest.fn(),
|
close: jest.fn(),
|
||||||
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
|
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||||
|
|
||||||
rustCrypto = new RustCrypto(olmMachine, httpApi, TEST_USER, TEST_DEVICE_ID);
|
outgoingRequestProcessor = {
|
||||||
|
makeOutgoingRequest: jest.fn(),
|
||||||
|
} as unknown as Mocked<OutgoingRequestProcessor>;
|
||||||
|
|
||||||
|
rustCrypto = new RustCrypto(olmMachine, {} as MatrixHttpApi<any>, TEST_USER, TEST_DEVICE_ID);
|
||||||
|
rustCrypto["outgoingRequestProcessor"] = outgoingRequestProcessor;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should poll for outgoing messages", () => {
|
it("should poll for outgoing messages and send them", async () => {
|
||||||
|
const testReq = new KeysQueryRequest("1234", "{}");
|
||||||
|
outgoingRequestQueue.push([testReq]);
|
||||||
|
|
||||||
|
const makeRequestPromise = awaitCallToMakeOutgoingRequest();
|
||||||
rustCrypto.onSyncCompleted({});
|
rustCrypto.onSyncCompleted({});
|
||||||
|
|
||||||
|
await makeRequestPromise;
|
||||||
expect(olmMachine.outgoingRequests).toHaveBeenCalled();
|
expect(olmMachine.outgoingRequests).toHaveBeenCalled();
|
||||||
});
|
expect(outgoingRequestProcessor.makeOutgoingRequest).toHaveBeenCalledWith(testReq);
|
||||||
|
|
||||||
/* simple requests that map directly to the request body */
|
|
||||||
const tests: Array<[any, "POST" | "PUT", string]> = [
|
|
||||||
[KeysUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/upload"],
|
|
||||||
[KeysQueryRequest, "POST", "https://example.com/_matrix/client/v3/keys/query"],
|
|
||||||
[KeysClaimRequest, "POST", "https://example.com/_matrix/client/v3/keys/claim"],
|
|
||||||
[SignatureUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/signatures/upload"],
|
|
||||||
[KeysBackupRequest, "PUT", "https://example.com/_matrix/client/v3/room_keys/keys"],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [RequestClass, expectedMethod, expectedPath] of tests) {
|
|
||||||
it(`should handle ${RequestClass.name}s`, async () => {
|
|
||||||
const testBody = '{ "foo": "bar" }';
|
|
||||||
const outgoingRequest = new RequestClass("1234", testBody);
|
|
||||||
outgoingRequestQueue.push([outgoingRequest]);
|
|
||||||
|
|
||||||
const testResponse = '{ "result": 1 }';
|
|
||||||
httpBackend
|
|
||||||
.when(expectedMethod, "/_matrix")
|
|
||||||
.check((req) => {
|
|
||||||
expect(req.path).toEqual(expectedPath);
|
|
||||||
expect(req.rawData).toEqual(testBody);
|
|
||||||
expect(req.headers["Accept"]).toEqual("application/json");
|
|
||||||
expect(req.headers["Content-Type"]).toEqual("application/json");
|
|
||||||
})
|
|
||||||
.respond(200, testResponse, true);
|
|
||||||
|
|
||||||
rustCrypto.onSyncCompleted({});
|
|
||||||
|
|
||||||
expect(olmMachine.outgoingRequests).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
const markSentCallPromise = awaitCallToMarkAsSent();
|
|
||||||
await httpBackend.flushAllExpected();
|
|
||||||
|
|
||||||
await markSentCallPromise;
|
|
||||||
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse);
|
|
||||||
httpBackend.verifyNoOutstandingRequests();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it("does not explode with unknown requests", async () => {
|
|
||||||
const outgoingRequest = { id: "5678", type: 987 };
|
|
||||||
outgoingRequestQueue.push([outgoingRequest]);
|
|
||||||
|
|
||||||
rustCrypto.onSyncCompleted({});
|
|
||||||
|
|
||||||
await awaitCallToMarkAsSent();
|
|
||||||
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("5678", 987, "");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stops looping when stop() is called", async () => {
|
it("stops looping when stop() is called", async () => {
|
||||||
const testResponse = '{ "result": 1 }';
|
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
outgoingRequestQueue.push([new KeysQueryRequest("1234", "{}")]);
|
outgoingRequestQueue.push([new KeysQueryRequest("1234", "{}")]);
|
||||||
httpBackend.when("POST", "/_matrix").respond(200, testResponse, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let makeRequestPromise = awaitCallToMakeOutgoingRequest();
|
||||||
|
|
||||||
rustCrypto.onSyncCompleted({});
|
rustCrypto.onSyncCompleted({});
|
||||||
|
|
||||||
expect(rustCrypto["outgoingRequestLoopRunning"]).toBeTruthy();
|
expect(rustCrypto["outgoingRequestLoopRunning"]).toBeTruthy();
|
||||||
|
|
||||||
// go a couple of times round the loop
|
// go a couple of times round the loop
|
||||||
await httpBackend.flush("/_matrix", 1);
|
let resolveMakeRequest = await makeRequestPromise;
|
||||||
await awaitCallToMarkAsSent();
|
makeRequestPromise = awaitCallToMakeOutgoingRequest();
|
||||||
|
resolveMakeRequest();
|
||||||
|
|
||||||
await httpBackend.flush("/_matrix", 1);
|
resolveMakeRequest = await makeRequestPromise;
|
||||||
await awaitCallToMarkAsSent();
|
makeRequestPromise = awaitCallToMakeOutgoingRequest();
|
||||||
|
resolveMakeRequest();
|
||||||
|
|
||||||
// a second sync while this is going on shouldn't make any difference
|
// a second sync while this is going on shouldn't make any difference
|
||||||
rustCrypto.onSyncCompleted({});
|
rustCrypto.onSyncCompleted({});
|
||||||
|
|
||||||
await httpBackend.flush("/_matrix", 1);
|
resolveMakeRequest = await makeRequestPromise;
|
||||||
await awaitCallToMarkAsSent();
|
outgoingRequestProcessor.makeOutgoingRequest.mockReset();
|
||||||
|
resolveMakeRequest();
|
||||||
|
|
||||||
// now stop...
|
// now stop...
|
||||||
rustCrypto.stop();
|
rustCrypto.stop();
|
||||||
@@ -241,7 +194,7 @@ describe("RustCrypto", () => {
|
|||||||
setTimeout(resolve, 100);
|
setTimeout(resolve, 100);
|
||||||
});
|
});
|
||||||
expect(rustCrypto["outgoingRequestLoopRunning"]).toBeFalsy();
|
expect(rustCrypto["outgoingRequestLoopRunning"]).toBeFalsy();
|
||||||
httpBackend.verifyNoOutstandingRequests();
|
expect(outgoingRequestProcessor.makeOutgoingRequest).not.toHaveBeenCalled();
|
||||||
expect(olmMachine.outgoingRequests).not.toHaveBeenCalled();
|
expect(olmMachine.outgoingRequests).not.toHaveBeenCalled();
|
||||||
|
|
||||||
// we sent three, so there should be 2 left
|
// we sent three, so there should be 2 left
|
109
src/rust-crypto/OutgoingRequestProcessor.ts
Normal file
109
src/rust-crypto/OutgoingRequestProcessor.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
OlmMachine,
|
||||||
|
KeysBackupRequest,
|
||||||
|
KeysClaimRequest,
|
||||||
|
KeysQueryRequest,
|
||||||
|
KeysUploadRequest,
|
||||||
|
RoomMessageRequest,
|
||||||
|
SignatureUploadRequest,
|
||||||
|
ToDeviceRequest,
|
||||||
|
} from "@matrix-org/matrix-sdk-crypto-js";
|
||||||
|
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { IHttpOpts, MatrixHttpApi, Method } from "../http-api";
|
||||||
|
import { QueryDict } from "../utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for all the request types returned by `OlmMachine.outgoingRequests`.
|
||||||
|
*/
|
||||||
|
export interface OutgoingRequest {
|
||||||
|
readonly id: string | undefined;
|
||||||
|
readonly type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OutgoingRequestManager: turns `OutgoingRequest`s from the rust sdk into HTTP requests
|
||||||
|
*
|
||||||
|
* We have one of these per `RustCrypto` (and hence per `MatrixClient`), not that it does anything terribly complicated.
|
||||||
|
* It's responsible for:
|
||||||
|
*
|
||||||
|
* * holding the reference to the `MatrixHttpApi`
|
||||||
|
* * turning `OutgoingRequest`s from the rust backend into HTTP requests, and sending them
|
||||||
|
* * sending the results of such requests back to the rust backend.
|
||||||
|
*/
|
||||||
|
export class OutgoingRequestProcessor {
|
||||||
|
public constructor(
|
||||||
|
private readonly olmMachine: OlmMachine,
|
||||||
|
private readonly http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async makeOutgoingRequest(msg: OutgoingRequest): Promise<void> {
|
||||||
|
let resp: string;
|
||||||
|
|
||||||
|
/* refer https://docs.rs/matrix-sdk-crypto/0.6.0/matrix_sdk_crypto/requests/enum.OutgoingRequests.html
|
||||||
|
* for the complete list of request types
|
||||||
|
*/
|
||||||
|
if (msg instanceof KeysUploadRequest) {
|
||||||
|
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/upload", {}, msg.body);
|
||||||
|
} else if (msg instanceof KeysQueryRequest) {
|
||||||
|
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/query", {}, msg.body);
|
||||||
|
} else if (msg instanceof KeysClaimRequest) {
|
||||||
|
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/claim", {}, msg.body);
|
||||||
|
} else if (msg instanceof SignatureUploadRequest) {
|
||||||
|
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/signatures/upload", {}, msg.body);
|
||||||
|
} else if (msg instanceof KeysBackupRequest) {
|
||||||
|
resp = await this.rawJsonRequest(Method.Put, "/_matrix/client/v3/room_keys/keys", {}, msg.body);
|
||||||
|
} else if (msg instanceof ToDeviceRequest) {
|
||||||
|
const path =
|
||||||
|
`/_matrix/client/v3/sendToDevice/${encodeURIComponent(msg.event_type)}/` +
|
||||||
|
encodeURIComponent(msg.txn_id);
|
||||||
|
resp = await this.rawJsonRequest(Method.Put, path, {}, msg.body);
|
||||||
|
} else if (msg instanceof RoomMessageRequest) {
|
||||||
|
const path =
|
||||||
|
`/_matrix/client/v3/room/${encodeURIComponent(msg.room_id)}/send/` +
|
||||||
|
`${encodeURIComponent(msg.event_type)}/${encodeURIComponent(msg.txn_id)}`;
|
||||||
|
resp = await this.rawJsonRequest(Method.Put, path, {}, msg.body);
|
||||||
|
} else {
|
||||||
|
logger.warn("Unsupported outgoing message", Object.getPrototypeOf(msg));
|
||||||
|
resp = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.id) {
|
||||||
|
await this.olmMachine.markRequestAsSent(msg.id, msg.type, resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async rawJsonRequest(method: Method, path: string, queryParams: QueryDict, body: string): Promise<string> {
|
||||||
|
const opts = {
|
||||||
|
// inhibit the JSON stringification and parsing within HttpApi.
|
||||||
|
json: false,
|
||||||
|
|
||||||
|
// nevertheless, we are sending, and accept, JSON.
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
|
||||||
|
// we use the full prefix
|
||||||
|
prefix: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.http.authedRequest<string>(method, path, queryParams, body, opts);
|
||||||
|
}
|
||||||
|
}
|
@@ -15,14 +15,6 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
|
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
|
||||||
import {
|
|
||||||
DecryptedRoomEvent,
|
|
||||||
KeysBackupRequest,
|
|
||||||
KeysClaimRequest,
|
|
||||||
KeysQueryRequest,
|
|
||||||
KeysUploadRequest,
|
|
||||||
SignatureUploadRequest,
|
|
||||||
} from "@matrix-org/matrix-sdk-crypto-js";
|
|
||||||
|
|
||||||
import type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto";
|
import type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto";
|
||||||
import type { IToDeviceEvent } from "../sync-accumulator";
|
import type { IToDeviceEvent } from "../sync-accumulator";
|
||||||
@@ -30,17 +22,9 @@ import type { IEncryptedEventInfo } from "../crypto/api";
|
|||||||
import { MatrixEvent } from "../models/event";
|
import { MatrixEvent } from "../models/event";
|
||||||
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { IHttpOpts, MatrixHttpApi, Method } from "../http-api";
|
import { IHttpOpts, MatrixHttpApi } from "../http-api";
|
||||||
import { QueryDict } from "../utils";
|
|
||||||
import { DeviceTrustLevel, UserTrustLevel } from "../crypto/CrossSigning";
|
import { DeviceTrustLevel, UserTrustLevel } from "../crypto/CrossSigning";
|
||||||
|
import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor";
|
||||||
/**
|
|
||||||
* Common interface for all the request types returned by `OlmMachine.outgoingRequests`.
|
|
||||||
*/
|
|
||||||
interface OutgoingRequest {
|
|
||||||
readonly id: string | undefined;
|
|
||||||
readonly type: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
|
* An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto.
|
||||||
@@ -55,12 +39,16 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
/** whether {@link outgoingRequestLoop} is currently running */
|
/** whether {@link outgoingRequestLoop} is currently running */
|
||||||
private outgoingRequestLoopRunning = false;
|
private outgoingRequestLoopRunning = false;
|
||||||
|
|
||||||
|
private outgoingRequestProcessor: OutgoingRequestProcessor;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly olmMachine: RustSdkCryptoJs.OlmMachine,
|
private readonly olmMachine: RustSdkCryptoJs.OlmMachine,
|
||||||
private readonly http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||||
_userId: string,
|
_userId: string,
|
||||||
_deviceId: string,
|
_deviceId: string,
|
||||||
) {}
|
) {
|
||||||
|
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
|
||||||
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
// stop() may be called multiple times, but attempting to close() the OlmMachine twice
|
// stop() may be called multiple times, but attempting to close() the OlmMachine twice
|
||||||
@@ -87,7 +75,7 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
origin_server_ts: event.getTs(),
|
origin_server_ts: event.getTs(),
|
||||||
}),
|
}),
|
||||||
new RustSdkCryptoJs.RoomId(event.getRoomId()!),
|
new RustSdkCryptoJs.RoomId(event.getRoomId()!),
|
||||||
)) as DecryptedRoomEvent;
|
)) as RustSdkCryptoJs.DecryptedRoomEvent;
|
||||||
return {
|
return {
|
||||||
clearEvent: JSON.parse(res.event),
|
clearEvent: JSON.parse(res.event),
|
||||||
claimedEd25519Key: res.senderClaimedEd25519Key,
|
claimedEd25519Key: res.senderClaimedEd25519Key,
|
||||||
@@ -190,7 +178,7 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const msg of outgoingRequests) {
|
for (const msg of outgoingRequests) {
|
||||||
await this.doOutgoingRequest(msg as OutgoingRequest);
|
await this.outgoingRequestProcessor.makeOutgoingRequest(msg as OutgoingRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -199,49 +187,4 @@ export class RustCrypto implements CryptoBackend {
|
|||||||
this.outgoingRequestLoopRunning = false;
|
this.outgoingRequestLoopRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doOutgoingRequest(msg: OutgoingRequest): Promise<void> {
|
|
||||||
let resp: string;
|
|
||||||
|
|
||||||
/* refer https://docs.rs/matrix-sdk-crypto/0.6.0/matrix_sdk_crypto/requests/enum.OutgoingRequests.html
|
|
||||||
* for the complete list of request types
|
|
||||||
*/
|
|
||||||
if (msg instanceof KeysUploadRequest) {
|
|
||||||
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/upload", {}, msg.body);
|
|
||||||
} else if (msg instanceof KeysQueryRequest) {
|
|
||||||
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/query", {}, msg.body);
|
|
||||||
} else if (msg instanceof KeysClaimRequest) {
|
|
||||||
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/claim", {}, msg.body);
|
|
||||||
} else if (msg instanceof SignatureUploadRequest) {
|
|
||||||
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/signatures/upload", {}, msg.body);
|
|
||||||
} else if (msg instanceof KeysBackupRequest) {
|
|
||||||
resp = await this.rawJsonRequest(Method.Put, "/_matrix/client/v3/room_keys/keys", {}, msg.body);
|
|
||||||
} else {
|
|
||||||
// TODO: ToDeviceRequest, RoomMessageRequest
|
|
||||||
logger.warn("Unsupported outgoing message", Object.getPrototypeOf(msg));
|
|
||||||
resp = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.id) {
|
|
||||||
await this.olmMachine.markRequestAsSent(msg.id, msg.type, resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async rawJsonRequest(method: Method, path: string, queryParams: QueryDict, body: string): Promise<string> {
|
|
||||||
const opts = {
|
|
||||||
// inhibit the JSON stringification and parsing within HttpApi.
|
|
||||||
json: false,
|
|
||||||
|
|
||||||
// nevertheless, we are sending, and accept, JSON.
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json",
|
|
||||||
},
|
|
||||||
|
|
||||||
// we use the full prefix
|
|
||||||
prefix: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
return await this.http.authedRequest<string>(method, path, queryParams, body, opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1398,7 +1398,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.2":
|
"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.3":
|
||||||
version "0.1.0-alpha.3"
|
version "0.1.0-alpha.3"
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.3.tgz#829ea03bcad8051dc1e4f0da18a66d4ba273f78f"
|
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.3.tgz#829ea03bcad8051dc1e4f0da18a66d4ba273f78f"
|
||||||
integrity sha512-KpEddjC34aobFlUYf2mIaXqkjLC0goRmYnbDZLTd0MwiFtau4b1TrPptQ8XFc90Z2VeAcvf18CBqA2otmZzUKQ==
|
integrity sha512-KpEddjC34aobFlUYf2mIaXqkjLC0goRmYnbDZLTd0MwiFtau4b1TrPptQ8XFc90Z2VeAcvf18CBqA2otmZzUKQ==
|
||||||
|
Reference in New Issue
Block a user