Compare commits

..

1 Commits

Author SHA1 Message Date
RiotRobot eff52b82e8 v34.1.0-rc.0 2024-07-09 12:04:20 +00:00
24 changed files with 1390 additions and 1029 deletions
-5
View File
@@ -316,11 +316,6 @@ jobs:
ref: staging
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version: "lts/*"
- name: Bump dependency
env:
DEPENDENCY: ${{ needs.npm.outputs.id }}
+1 -11
View File
@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
specs: [integ, unit]
node: ["lts/*", 22]
node: ["lts/*", 21, 22]
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -63,16 +63,6 @@ jobs:
coverage
!coverage/lcov-report
# Dummy completion job to simplify branch protections
jest-complete:
name: Jest tests
needs: jest
if: always()
runs-on: ubuntu-latest
steps:
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
run: exit 1
matrix-react-sdk:
name: Downstream test matrix-react-sdk
if: github.event_name == 'merge_group'
+1 -1
View File
@@ -39,7 +39,7 @@ client.publicRooms(function (err, data) {
```
See below for how to include libolm to enable end-to-end-encryption. Please check
[the Node.js terminal app](examples/node/README.md) for a more complex example.
[the Node.js terminal app](examples/node) for a more complex example.
To start the client:
+9 -8
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "34.0.0",
"version": "34.1.0-rc.0",
"description": "Matrix Client-Server SDK for Javascript",
"engines": {
"node": ">=20.0.0"
@@ -31,8 +31,8 @@
"keywords": [
"matrix-org"
],
"main": "./src/index.ts",
"browser": "./src/browser-index.ts",
"main": "./lib/index.js",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.ts",
"matrix_lib_main": "./lib/index.js",
@@ -53,7 +53,7 @@
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0",
"@matrix-org/matrix-sdk-crypto-wasm": "^6.0.0",
"another-json": "^0.2.0",
"bs58": "^6.0.0",
"content-type": "^1.0.4",
@@ -106,7 +106,7 @@
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^54.0.0",
"fake-indexeddb": "^5.0.2",
"fetch-mock": "10.1.0",
"fetch-mock": "10.0.7",
"fetch-mock-jest": "^1.5.1",
"husky": "^9.0.0",
"jest": "^29.0.0",
@@ -117,8 +117,8 @@
"lint-staged": "^15.0.2",
"matrix-mock-request": "^2.5.0",
"node-fetch": "^2.7.0",
"prettier": "3.3.3",
"rimraf": "^6.0.0",
"prettier": "3.3.2",
"rimraf": "^5.0.0",
"ts-node": "^10.9.2",
"typedoc": "^0.26.0",
"typedoc-plugin-coverage": "^3.0.0",
@@ -130,5 +130,6 @@
"outputDirectory": "coverage",
"outputName": "jest-sonar-report.xml",
"relativePaths": true
}
},
"typings": "./lib/index.d.ts"
}
+2 -78
View File
@@ -2333,84 +2333,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
});
describe("m.room_key.withheld handling", () => {
describe.each([
["m.blacklisted", "The sender has blocked you.", DecryptionFailureCode.MEGOLM_KEY_WITHHELD],
[
"m.unverified",
"The sender has disabled encrypting to unverified devices.",
DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE,
],
])(
"Decryption fails with withheld error if a withheld notice with code '%s' is received",
(withheldCode, expectedMessage, expectedErrorCode) => {
it.each(["before", "after"])("%s the event", async (when) => {
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
await startClientAndAwaitFirstSync();
// A promise which resolves, with the MatrixEvent which wraps the event, once the decryption fails.
let awaitDecryption = emitPromise(aliceClient, MatrixEventEvent.Decrypted);
// Send Alice an encrypted room event which looks like it was encrypted with a megolm session
async function sendEncryptedEvent() {
const event = {
...testData.ENCRYPTED_EVENT,
origin_server_ts: Date.now(),
};
const syncResponse = {
next_batch: 1,
rooms: { join: { [ROOM_ID]: { timeline: { events: [event] } } } },
};
syncResponder.sendOrQueueSyncResponse(syncResponse);
await syncPromise(aliceClient);
}
// Send Alice a withheld notice
async function sendWithheldMessage() {
const withheldMessage = {
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: ROOM_ID,
sender_key: testData.ENCRYPTED_EVENT.content!.sender_key,
session_id: testData.ENCRYPTED_EVENT.content!.session_id,
code: withheldCode,
reason: "zzz",
},
};
syncResponder.sendOrQueueSyncResponse({
next_batch: 1,
to_device: { events: [withheldMessage] },
});
await syncPromise(aliceClient);
}
if (when === "before") {
await sendWithheldMessage();
await sendEncryptedEvent();
} else {
await sendEncryptedEvent();
// Make sure that the first attempt to decrypt has happened before the withheld arrives
await awaitDecryption;
awaitDecryption = emitPromise(aliceClient, MatrixEventEvent.Decrypted);
await sendWithheldMessage();
}
const ev = await awaitDecryption;
expect(ev.getContent()).toEqual({
body: `** Unable to decrypt: DecryptionError: ${expectedMessage} **`,
msgtype: "m.bad.encrypted",
});
expect(ev.decryptionFailureReason).toEqual(expectedErrorCode);
// `isEncryptedDisabledForUnverifiedDevices` should be true for `m.unverified` and false for other errors.
expect(ev.isEncryptedDisabledForUnverifiedDevices).toEqual(withheldCode === "m.unverified");
});
},
);
// TODO: there are a bunch more tests for this sort of thing in spec/unit/crypto/algorithms/megolm.spec.ts.
// They should be converted to integ tests and moved.
oldBackendOnly("does not block decryption on an 'm.unavailable' report", async function () {
// there may be a key downloads for alice
+1 -6
View File
@@ -412,12 +412,7 @@ describe("MatrixEvent", () => {
const crypto = {
decryptEvent: jest
.fn()
.mockRejectedValue(
new DecryptionError(
DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE,
"The sender has disabled encrypting to unverified devices.",
),
),
.mockRejectedValue("DecryptionError: The sender has disabled encrypting to unverified devices."),
} as unknown as Crypto;
await encryptedEvent.attemptDecryption(crypto);
@@ -95,7 +95,6 @@ describe("initRustCrypto", () => {
deleteSecretsFromInbox: jest.fn(),
registerReceiveSecretCallback: jest.fn(),
registerDevicesUpdatedCallback: jest.fn(),
registerRoomKeysWithheldCallback: jest.fn(),
outgoingRequests: jest.fn(),
isBackupEnabled: jest.fn().mockResolvedValue(false),
verifyBackup: jest.fn().mockResolvedValue({ trusted: jest.fn().mockReturnValue(false) }),
+1 -1
View File
@@ -434,7 +434,7 @@ describe("TimelineWindow", function () {
});
function idsOf(events: Array<MatrixEvent>): Array<string> {
return events.map((e) => (e ? (e.getId() ?? "MISSING_ID") : "MISSING_EVENT"));
return events.map((e) => (e ? e.getId() ?? "MISSING_ID" : "MISSING_EVENT"));
}
describe("removing events", () => {
+1 -1
View File
@@ -105,7 +105,7 @@ const mockGetStateEvents =
(events: MatrixEvent[] = FAKE_STATE_EVENTS as MatrixEvent[]) =>
(type: EventType, userId?: string): MatrixEvent[] | MatrixEvent | null => {
if (type === EventType.GroupCallMemberPrefix) {
return userId === undefined ? events : (events.find((e) => e.getStateKey() === userId) ?? null);
return userId === undefined ? events : events.find((e) => e.getStateKey() === userId) ?? null;
} else {
const fakeEvent = { getContent: () => ({}), getTs: () => 0 } as MatrixEvent;
return userId === undefined ? [fakeEvent] : fakeEvent;
+3 -6
View File
@@ -324,13 +324,10 @@ export interface ICreateClientOpts {
localTimeoutMs?: number;
/**
* Set to false to send the access token to the server via a query parameter rather
* than the Authorization HTTP header.
* Set to true to use
* Authorization header instead of query param to send the access token to the server.
*
* Note that as of v1.11 of the Matrix spec, sending the access token via a query
* is deprecated.
*
* Default true.
* Default false.
*/
useAuthorizationHeader?: boolean;
+2 -13
View File
@@ -557,12 +557,6 @@ export enum DecryptionFailureCode {
/** Message was encrypted with a Megolm session whose keys have not been shared with us. */
MEGOLM_UNKNOWN_INBOUND_SESSION_ID = "MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
/** A special case of {@link MEGOLM_UNKNOWN_INBOUND_SESSION_ID}: the sender has told us it is withholding the key. */
MEGOLM_KEY_WITHHELD = "MEGOLM_KEY_WITHHELD",
/** A special case of {@link MEGOLM_KEY_WITHHELD}: the sender has told us it is withholding the key, because the current device is unverified. */
MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE = "MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE",
/** Message was encrypted with a Megolm session which has been shared with us, but in a later ratchet state. */
OLM_UNKNOWN_MESSAGE_INDEX = "OLM_UNKNOWN_MESSAGE_INDEX",
@@ -854,14 +848,9 @@ export interface CreateSecretStorageOpts {
setupNewSecretStorage?: boolean;
/**
* Function called to get the user's current key backup passphrase.
*
* Should return a promise that resolves with a Uint8Array
* Function called to get the user's
* current key backup passphrase. Should return a promise that resolves with a Uint8Array
* containing the key, or rejects if the key cannot be obtained.
*
* Only used when the client has existing key backup, but no secret storage.
*
* @deprecated Not used by the Rust crypto stack.
*/
getKeyBackupPassphrase?: () => Promise<Uint8Array>;
}
+14 -14
View File
@@ -1221,13 +1221,13 @@ export class OlmDevice {
this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
if (session === null || sessionData === null) {
if (withheld) {
const failureCode =
withheld.code === "m.unverified"
? DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE
: DecryptionFailureCode.MEGOLM_KEY_WITHHELD;
error = new DecryptionError(failureCode, calculateWithheldMessage(withheld), {
session: senderKey + "|" + sessionId,
});
error = new DecryptionError(
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
calculateWithheldMessage(withheld),
{
session: senderKey + "|" + sessionId,
},
);
}
result = null;
return;
@@ -1237,13 +1237,13 @@ export class OlmDevice {
res = session.decrypt(body);
} catch (e) {
if ((<Error>e)?.message === "OLM.UNKNOWN_MESSAGE_INDEX" && withheld) {
const failureCode =
withheld.code === "m.unverified"
? DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE
: DecryptionFailureCode.MEGOLM_KEY_WITHHELD;
error = new DecryptionError(failureCode, calculateWithheldMessage(withheld), {
session: senderKey + "|" + sessionId,
});
error = new DecryptionError(
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
calculateWithheldMessage(withheld),
{
session: senderKey + "|" + sessionId,
},
);
} else {
error = <Error>e;
}
+2 -2
View File
@@ -258,7 +258,7 @@ export class RoomWidgetClient extends MatrixClient {
}
room.updatePendingEvent(event, EventStatus.SENT, response.event_id);
return { event_id: response.event_id! };
return { event_id: response.event_id };
}
public async sendStateEvent(
@@ -267,7 +267,7 @@ export class RoomWidgetClient extends MatrixClient {
content: any,
stateKey = "",
): Promise<ISendEventResponse> {
return (await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId)) as ISendEventResponse;
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
}
public async sendToDevice(eventType: string, contentMap: SendToDeviceContentMap): Promise<{}> {
+10 -11
View File
@@ -160,26 +160,25 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
const callMemberships: CallMembership[] = [];
for (const memberEvent of callMemberEvents) {
const content = memberEvent.getContent();
const eventKeysCount = Object.keys(content).length;
// Dont even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
if (eventKeysCount === 0) continue;
let membershipContents: any[] = [];
// We first decide if its a MSC4143 event (per device state key)
if (eventKeysCount > 1 && "focus_active" in content) {
// We have a MSC4143 event membership event
membershipContents.push(content);
} else if (eventKeysCount === 1 && "memberships" in content) {
if ("memberships" in content) {
// we have a legacy (one event for all devices) event
if (!Array.isArray(content["memberships"])) {
logger.warn(`Malformed member event from ${memberEvent.getSender()}: memberships is not an array`);
continue;
}
membershipContents = content["memberships"];
} else {
// We have a MSC4143 event membership event
if (Object.keys(content).length !== 0) {
// We checked for empty content to not try to construct CallMembership's with {}.
membershipContents.push(content);
}
}
if (membershipContents.length === 0) {
continue;
}
if (membershipContents.length === 0) continue;
for (const membershipData of membershipContents) {
try {
-22
View File
@@ -1,22 +0,0 @@
/*
Copyright 2024 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.
*/
export * from "./CallMembership";
export * from "./focus";
export * from "./LivekitFocus";
export * from "./MatrixRTCSession";
export * from "./MatrixRTCSessionManager";
export * from "./types";
+12 -5
View File
@@ -43,6 +43,7 @@ import { MatrixError } from "../http-api";
import { TypedEventEmitter } from "./typed-event-emitter";
import { EventStatus } from "./event-status";
import { CryptoBackend, DecryptionError } from "../common-crypto/CryptoBackend";
import { WITHHELD_MESSAGES } from "../crypto/OlmDevice";
import { IAnnotatedPushRule } from "../@types/PushRules";
import { Room } from "./room";
import { EventTimeline } from "./event-timeline";
@@ -311,6 +312,12 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
private thread?: Thread;
private threadId?: string;
/*
* True if this event is an encrypted event which we failed to decrypt, the receiver's device is unverified and
* the sender has disabled encrypting to unverified devices.
*/
private encryptedDisabledForUnverifiedDevices = false;
/* Set an approximate timestamp for the event relative the local clock.
* This will inherently be approximate because it doesn't take into account
* the time between the server putting the 'age' field on the event as it sent
@@ -404,7 +411,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
// The fallback in these cases will be to use the origin_server_ts.
// For EDUs, the origin_server_ts also is not defined so we use Date.now().
const age = this.getAge();
this.localTimestamp = age !== undefined ? Date.now() - age : (this.getTs() ?? Date.now());
this.localTimestamp = age !== undefined ? Date.now() - age : this.getTs() ?? Date.now();
this.reEmitter = new TypedReEmitter(this);
}
@@ -780,14 +787,12 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
return this._decryptionFailureReason;
}
/**
/*
* True if this event is an encrypted event which we failed to decrypt, the receiver's device is unverified and
* the sender has disabled encrypting to unverified devices.
*
* @deprecated: Prefer `event.decryptionFailureReason === DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE`.
*/
public get isEncryptedDisabledForUnverifiedDevices(): boolean {
return this.decryptionFailureReason === DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE;
return this.isDecryptionFailure() && this.encryptedDisabledForUnverifiedDevices;
}
public shouldAttemptDecryption(): boolean {
@@ -977,6 +982,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.claimedEd25519Key = decryptionResult.claimedEd25519Key ?? null;
this.forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain || [];
this.untrusted = decryptionResult.untrusted || false;
this.encryptedDisabledForUnverifiedDevices = false;
this.invalidateExtensibleEvent();
}
@@ -997,6 +1003,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.claimedEd25519Key = null;
this.forwardingCurve25519KeyChain = [];
this.untrusted = false;
this.encryptedDisabledForUnverifiedDevices = reason === `DecryptionError: ${WITHHELD_MESSAGES["m.unverified"]}`;
this.invalidateExtensibleEvent();
}
+2 -3
View File
@@ -24,7 +24,7 @@ import {
} from "./event-timeline-set";
import { Direction, EventTimeline } from "./event-timeline";
import { getHttpUriForMxc } from "../content-repo";
import { removeElement } from "../utils";
import { compare, removeElement } from "../utils";
import { normalize, noUnsafeEventProps } from "../utils";
import { IEvent, IThreadBundledRelationship, MatrixEvent, MatrixEventEvent, MatrixEventHandlerMap } from "./event";
import { EventStatus } from "./event-status";
@@ -3451,8 +3451,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
return true;
});
// make sure members have stable order
const collator = new Intl.Collator();
otherMembers.sort((a, b) => collator.compare(a.userId, b.userId));
otherMembers.sort((a, b) => compare(a.userId, b.userId));
// only 5 first members, immitate summaryHeroes
otherMembers = otherMembers.slice(0, 5);
otherNames = otherMembers.map((m) => m.name);
+6 -10
View File
@@ -14,17 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
import {
CollectStrategy,
EncryptionAlgorithm,
EncryptionSettings,
HistoryVisibility as RustHistoryVisibility,
OlmMachine,
RoomId,
ToDeviceRequest,
UserId,
HistoryVisibility as RustHistoryVisibility,
ToDeviceRequest,
} from "@matrix-org/matrix-sdk-crypto-wasm";
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
import { EventType } from "../@types/event";
import { IContent, MatrixEvent } from "../models/event";
@@ -143,7 +142,7 @@ export class RoomEncryptor {
* @param globalBlacklistUnverifiedDevices - When `true`, it will not send encrypted messages to unverified devices
*/
public encryptEvent(event: MatrixEvent | null, globalBlacklistUnverifiedDevices: boolean): Promise<void> {
const logger = new LogSpan(this.prefixedLogger, event ? (event.getTxnId() ?? "") : "prepareForEncryption");
const logger = new LogSpan(this.prefixedLogger, event ? event.getTxnId() ?? "" : "prepareForEncryption");
// Ensure order of encryption to avoid message ordering issues, as the scheduler only ensures
// events order after they have been encrypted.
const prom = this.currentEncryptionPromise
@@ -253,11 +252,8 @@ export class RoomEncryptor {
// When this.room.getBlacklistUnverifiedDevices() === null, the global settings should be used
// See Room#getBlacklistUnverifiedDevices
if (this.room.getBlacklistUnverifiedDevices() ?? globalBlacklistUnverifiedDevices) {
rustEncryptionSettings.sharingStrategy = CollectStrategy.DeviceBasedStrategyOnlyTrustedDevices;
} else {
rustEncryptionSettings.sharingStrategy = CollectStrategy.DeviceBasedStrategyAllDevices;
}
rustEncryptionSettings.onlyAllowTrustedDevices =
this.room.getBlacklistUnverifiedDevices() ?? globalBlacklistUnverifiedDevices;
await logDuration(this.prefixedLogger, "shareRoomKey", async () => {
const shareMessages: ToDeviceRequest[] = await this.olmMachine.shareRoomKey(
-3
View File
@@ -174,9 +174,6 @@ async function initOlmMachine(
await olmMachine.registerRoomKeyUpdatedCallback((sessions: RustSdkCryptoJs.RoomKeyInfo[]) =>
rustCrypto.onRoomKeysUpdated(sessions),
);
await olmMachine.registerRoomKeysWithheldCallback((withheld: RustSdkCryptoJs.RoomKeyWithheldInfo[]) =>
rustCrypto.onRoomKeysWithheld(withheld),
);
await olmMachine.registerUserIdentityUpdatedCallback((userId: RustSdkCryptoJs.UserId) =>
rustCrypto.onUserIdentityUpdated(userId),
);
+24 -62
View File
@@ -1486,7 +1486,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
this.logger.debug(
`Got update for session ${key.senderKey.toBase64()}|${key.sessionId} in ${key.roomId.toString()}`,
);
const pendingList = this.eventDecryptor.getEventsPendingRoomKey(key.roomId.toString(), key.sessionId);
const pendingList = this.eventDecryptor.getEventsPendingRoomKey(key);
if (pendingList.length === 0) return;
this.logger.debug(
@@ -1507,37 +1507,6 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
}
}
/**
* Callback for `OlmMachine.registerRoomKeyWithheldCallback`.
*
* Called by the rust sdk whenever we are told that a key has been withheld. We see if we had any events that
* failed to decrypt for the given session, and update their status if so.
*
* @param withheld - Details of the withheld sessions.
*/
public async onRoomKeysWithheld(withheld: RustSdkCryptoJs.RoomKeyWithheldInfo[]): Promise<void> {
for (const session of withheld) {
this.logger.debug(`Got withheld message for session ${session.sessionId} in ${session.roomId.toString()}`);
const pendingList = this.eventDecryptor.getEventsPendingRoomKey(
session.roomId.toString(),
session.sessionId,
);
if (pendingList.length === 0) return;
// The easiest way to update the status of the event is to have another go at decrypting it.
this.logger.debug(
"Retrying decryption on events:",
pendingList.map((e) => `${e.getId()}`),
);
for (const ev of pendingList) {
ev.attemptDecryption(this, { isRetry: true }).catch((_e) => {
// It's somewhat expected that we still can't decrypt here.
});
}
}
}
/**
* Callback for `OlmMachine.registerUserIdentityUpdatedCallback`
*
@@ -1714,7 +1683,7 @@ class EventDecryptor {
/**
* Events which we couldn't decrypt due to unknown sessions / indexes.
*
* Map from roomId to sessionId to Set of MatrixEvents
* Map from senderKey to sessionId to Set of MatrixEvents
*/
private eventsPendingKey = new MapWithDefault<string, MapWithDefault<string, Set<MatrixEvent>>>(
() => new MapWithDefault<string, Set<MatrixEvent>>(() => new Set()),
@@ -1818,17 +1787,6 @@ class EventDecryptor {
}
}
// If we got a withheld code, expose that.
if (err.maybe_withheld) {
// Unfortunately the Rust SDK API doesn't let us distinguish between different withheld cases, other than
// by string-matching.
const failureCode =
err.maybe_withheld === "The sender has disabled encrypting to unverified devices."
? DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE
: DecryptionFailureCode.MEGOLM_KEY_WITHHELD;
throw new DecryptionError(failureCode, err.maybe_withheld, errorDetails);
}
switch (err.code) {
case RustSdkCryptoJs.DecryptionErrorCode.MissingRoomKey:
throw new DecryptionError(
@@ -1874,27 +1832,30 @@ class EventDecryptor {
* Look for events which are waiting for a given megolm session
*
* Returns a list of events which were encrypted by `session` and could not be decrypted
*
* @param session -
*/
public getEventsPendingRoomKey(roomId: string, sessionId: string): MatrixEvent[] {
const roomPendingEvents = this.eventsPendingKey.get(roomId);
if (!roomPendingEvents) return [];
public getEventsPendingRoomKey(session: RustSdkCryptoJs.RoomKeyInfo): MatrixEvent[] {
const senderPendingEvents = this.eventsPendingKey.get(session.senderKey.toBase64());
if (!senderPendingEvents) return [];
const sessionPendingEvents = roomPendingEvents.get(sessionId);
const sessionPendingEvents = senderPendingEvents.get(session.sessionId);
if (!sessionPendingEvents) return [];
return [...sessionPendingEvents];
const roomId = session.roomId.toString();
return [...sessionPendingEvents].filter((ev) => ev.getRoomId() === roomId);
}
/**
* Add an event to the list of those awaiting their session keys.
*/
private addEventToPendingList(event: MatrixEvent): void {
const roomId = event.getRoomId();
// We shouldn't have events without a room id here.
if (!roomId) return;
const content = event.getWireContent();
const senderKey = content.sender_key;
const sessionId = content.session_id;
const roomPendingEvents = this.eventsPendingKey.getOrCreate(roomId);
const sessionPendingEvents = roomPendingEvents.getOrCreate(event.getWireContent().session_id);
const senderPendingEvents = this.eventsPendingKey.getOrCreate(senderKey);
const sessionPendingEvents = senderPendingEvents.getOrCreate(sessionId);
sessionPendingEvents.add(event);
}
@@ -1902,22 +1863,23 @@ class EventDecryptor {
* Remove an event from the list of those awaiting their session keys.
*/
private removeEventFromPendingList(event: MatrixEvent): void {
const roomId = event.getRoomId();
if (!roomId) return;
const content = event.getWireContent();
const senderKey = content.sender_key;
const sessionId = content.session_id;
const roomPendingEvents = this.eventsPendingKey.getOrCreate(roomId);
if (!roomPendingEvents) return;
const senderPendingEvents = this.eventsPendingKey.get(senderKey);
if (!senderPendingEvents) return;
const sessionPendingEvents = roomPendingEvents.get(event.getWireContent().session_id);
const sessionPendingEvents = senderPendingEvents.get(sessionId);
if (!sessionPendingEvents) return;
sessionPendingEvents.delete(event);
// also clean up the higher-level maps if they are now empty
if (sessionPendingEvents.size === 0) {
roomPendingEvents.delete(event.getWireContent().session_id);
if (roomPendingEvents.size === 0) {
this.eventsPendingKey.delete(roomId);
senderPendingEvents.delete(sessionId);
if (senderPendingEvents.size === 0) {
this.eventsPendingKey.delete(senderKey);
}
}
}
+1 -1
View File
@@ -504,7 +504,7 @@ export class SyncAccumulator {
currentData._timeline.push({
event: transformedEvent,
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
token: index === 0 ? data.timeline.prev_batch ?? null : null,
});
});
+1 -3
View File
@@ -1403,9 +1403,7 @@ export class SyncApi {
if (limited) {
room.resetLiveTimeline(
joinObj.timeline.prev_batch,
this.syncOpts.canResetEntireTimeline!(room.roomId)
? null
: (syncEventData.oldSyncToken ?? null),
this.syncOpts.canResetEntireTimeline!(room.roomId) ? null : syncEventData.oldSyncToken ?? null,
);
// We have to assume any gap in any timeline is
+10
View File
@@ -649,6 +649,16 @@ export function lexicographicCompare(a: string, b: string): number {
}
}
const collator = new Intl.Collator();
/**
* Performant language-sensitive string comparison
* @param a - the first string to compare
* @param b - the second string to compare
*/
export function compare(a: string, b: string): number {
return collator.compare(a, b);
}
/**
* This function is similar to Object.assign() but it assigns recursively and
* allows you to ignore nullish values from the source
+1287 -762
View File
File diff suppressed because it is too large Load Diff