Skip to content

Commit a3248c0

Browse files
committed
Merge branch 'develop' into kegan/sync-v3
2 parents e05f9b5 + c96f1ba commit a3248c0

File tree

12 files changed

+256
-86
lines changed

12 files changed

+256
-86
lines changed

spec/unit/queueToDevice.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ describe.each([
244244

245245
client.retryImmediately();
246246

247-
expect(await httpBackend.flush(null, 1, 20)).toEqual(1);
247+
// longer timeout here to try & avoid flakiness
248+
expect(await httpBackend.flush(null, 1, 3000)).toEqual(1);
248249
});
249250

250251
it("retries on when client is started", async function() {

spec/unit/room.spec.ts

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2435,16 +2435,96 @@ describe("Room", function() {
24352435
expect(room.getEventReadUpTo(userA)).toEqual("eventId");
24362436
});
24372437

2438-
it("prefers older receipt", () => {
2439-
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
2440-
return (receiptType === ReceiptType.Read
2441-
? { eventId: "eventId1" }
2442-
: { eventId: "eventId2" }
2443-
) as IWrappedReceipt;
2444-
};
2445-
room.getUnfilteredTimelineSet = () => ({ compareEventOrdering: (event1, event2) => 1 } as EventTimelineSet);
2438+
describe("prefers newer receipt", () => {
2439+
it("should compare correctly using timelines", () => {
2440+
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
2441+
if (receiptType === ReceiptType.ReadPrivate) {
2442+
return { eventId: "eventId1" } as IWrappedReceipt;
2443+
}
2444+
if (receiptType === ReceiptType.UnstableReadPrivate) {
2445+
return { eventId: "eventId2" } as IWrappedReceipt;
2446+
}
2447+
if (receiptType === ReceiptType.Read) {
2448+
return { eventId: "eventId3" } as IWrappedReceipt;
2449+
}
2450+
};
2451+
2452+
for (let i = 1; i <= 3; i++) {
2453+
room.getUnfilteredTimelineSet = () => ({ compareEventOrdering: (event1, event2) => {
2454+
return (event1 === `eventId${i}`) ? 1 : -1;
2455+
} } as EventTimelineSet);
2456+
2457+
expect(room.getEventReadUpTo(userA)).toEqual(`eventId${i}`);
2458+
}
2459+
});
2460+
2461+
it("should compare correctly by timestamp", () => {
2462+
for (let i = 1; i <= 3; i++) {
2463+
room.getUnfilteredTimelineSet = () => ({
2464+
compareEventOrdering: (_1, _2) => null,
2465+
} as EventTimelineSet);
2466+
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
2467+
if (receiptType === ReceiptType.ReadPrivate) {
2468+
return { eventId: "eventId1", data: { ts: i === 1 ? 1 : 0 } } as IWrappedReceipt;
2469+
}
2470+
if (receiptType === ReceiptType.UnstableReadPrivate) {
2471+
return { eventId: "eventId2", data: { ts: i === 2 ? 1 : 0 } } as IWrappedReceipt;
2472+
}
2473+
if (receiptType === ReceiptType.Read) {
2474+
return { eventId: "eventId3", data: { ts: i === 3 ? 1 : 0 } } as IWrappedReceipt;
2475+
}
2476+
};
2477+
2478+
expect(room.getEventReadUpTo(userA)).toEqual(`eventId${i}`);
2479+
}
2480+
});
24462481

2447-
expect(room.getEventReadUpTo(userA)).toEqual("eventId1");
2482+
describe("fallback precedence", () => {
2483+
beforeAll(() => {
2484+
room.getUnfilteredTimelineSet = () => ({
2485+
compareEventOrdering: (_1, _2) => null,
2486+
} as EventTimelineSet);
2487+
});
2488+
2489+
it("should give precedence to m.read.private", () => {
2490+
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
2491+
if (receiptType === ReceiptType.ReadPrivate) {
2492+
return { eventId: "eventId1" } as IWrappedReceipt;
2493+
}
2494+
if (receiptType === ReceiptType.UnstableReadPrivate) {
2495+
return { eventId: "eventId2" } as IWrappedReceipt;
2496+
}
2497+
if (receiptType === ReceiptType.Read) {
2498+
return { eventId: "eventId3" } as IWrappedReceipt;
2499+
}
2500+
};
2501+
2502+
expect(room.getEventReadUpTo(userA)).toEqual(`eventId1`);
2503+
});
2504+
2505+
it("should give precedence to org.matrix.msc2285.read.private", () => {
2506+
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
2507+
if (receiptType === ReceiptType.UnstableReadPrivate) {
2508+
return { eventId: "eventId2" } as IWrappedReceipt;
2509+
}
2510+
if (receiptType === ReceiptType.Read) {
2511+
return { eventId: "eventId2" } as IWrappedReceipt;
2512+
}
2513+
};
2514+
2515+
expect(room.getEventReadUpTo(userA)).toEqual(`eventId2`);
2516+
});
2517+
2518+
it("should give precedence to m.read", () => {
2519+
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
2520+
if (receiptType === ReceiptType.Read) {
2521+
return { eventId: "eventId3" } as IWrappedReceipt;
2522+
}
2523+
};
2524+
2525+
expect(room.getEventReadUpTo(userA)).toEqual(`eventId3`);
2526+
});
2527+
});
24482528
});
24492529
});
24502530
});

spec/unit/sync-accumulator.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ describe("SyncAccumulator", function() {
302302
[ReceiptType.ReadPrivate]: {
303303
"@dan:localhost": { ts: 4 },
304304
},
305+
[ReceiptType.UnstableReadPrivate]: {
306+
"@matthew:localhost": { ts: 5 },
307+
},
305308
"some.other.receipt.type": {
306309
"@should_be_ignored:localhost": { key: "val" },
307310
},
@@ -347,6 +350,9 @@ describe("SyncAccumulator", function() {
347350
[ReceiptType.ReadPrivate]: {
348351
"@dan:localhost": { ts: 4 },
349352
},
353+
[ReceiptType.UnstableReadPrivate]: {
354+
"@matthew:localhost": { ts: 5 },
355+
},
350356
},
351357
"$event2:localhost": {
352358
[ReceiptType.Read]: {

spec/unit/utils.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { logger } from "../../src/logger";
1616
import { mkMessage } from "../test-utils/test-utils";
1717
import { makeBeaconEvent } from "../test-utils/beacon";
18+
import { ReceiptType } from "../../src/@types/read_receipts";
1819

1920
// TODO: Fix types throughout
2021

@@ -523,4 +524,54 @@ describe("utils", function() {
523524
).toEqual([beaconEvent2, beaconEvent1, beaconEvent3]);
524525
});
525526
});
527+
528+
describe('getPrivateReadReceiptField', () => {
529+
it('should return m.read.private if server supports stable', async () => {
530+
expect(await utils.getPrivateReadReceiptField({
531+
doesServerSupportUnstableFeature: jest.fn().mockImplementation((feature) => {
532+
return feature === "org.matrix.msc2285.stable";
533+
}),
534+
} as any)).toBe(ReceiptType.ReadPrivate);
535+
});
536+
537+
it('should return m.read.private if server supports stable and unstable', async () => {
538+
expect(await utils.getPrivateReadReceiptField({
539+
doesServerSupportUnstableFeature: jest.fn().mockImplementation((feature) => {
540+
return ["org.matrix.msc2285.stable", "org.matrix.msc2285"].includes(feature);
541+
}),
542+
} as any)).toBe(ReceiptType.ReadPrivate);
543+
});
544+
545+
it('should return org.matrix.msc2285.read.private if server supports unstable', async () => {
546+
expect(await utils.getPrivateReadReceiptField({
547+
doesServerSupportUnstableFeature: jest.fn().mockImplementation((feature) => {
548+
return feature === "org.matrix.msc2285";
549+
}),
550+
} as any)).toBe(ReceiptType.UnstableReadPrivate);
551+
});
552+
553+
it('should return none if server does not support either', async () => {
554+
expect(await utils.getPrivateReadReceiptField({
555+
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false),
556+
} as any)).toBeFalsy();
557+
});
558+
});
559+
560+
describe('isSupportedReceiptType', () => {
561+
it('should support m.read', () => {
562+
expect(utils.isSupportedReceiptType(ReceiptType.Read)).toBeTruthy();
563+
});
564+
565+
it('should support m.read.private', () => {
566+
expect(utils.isSupportedReceiptType(ReceiptType.ReadPrivate)).toBeTruthy();
567+
});
568+
569+
it('should support org.matrix.msc2285.read.private', () => {
570+
expect(utils.isSupportedReceiptType(ReceiptType.UnstableReadPrivate)).toBeTruthy();
571+
});
572+
573+
it('should not support other receipt types', () => {
574+
expect(utils.isSupportedReceiptType("this is a receipt type")).toBeFalsy();
575+
});
576+
});
526577
});

src/@types/read_receipts.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@ limitations under the License.
1717
export enum ReceiptType {
1818
Read = "m.read",
1919
FullyRead = "m.fully_read",
20-
ReadPrivate = "org.matrix.msc2285.read.private"
20+
ReadPrivate = "m.read.private",
21+
/**
22+
* @deprecated Please use the ReadPrivate type when possible. This value may be removed at any time without notice.
23+
*/
24+
UnstableReadPrivate = "org.matrix.msc2285.read.private",
2125
}

src/client.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import { sleep } from './utils';
4040
import { Direction, EventTimeline } from "./models/event-timeline";
4141
import { IActionsObject, PushProcessor } from "./pushprocessor";
4242
import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
43-
import { IEncryptAndSendToDevicesResult } from "./crypto";
4443
import * as olmlib from "./crypto/olmlib";
4544
import { decodeBase64, encodeBase64 } from "./crypto/olmlib";
4645
import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice";
@@ -1089,11 +1088,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
10891088
// Figure out if we've read something or if it's just informational
10901089
const content = event.getContent();
10911090
const isSelf = Object.keys(content).filter(eid => {
1092-
const read = content[eid][ReceiptType.Read];
1093-
if (read && Object.keys(read).includes(this.getUserId())) return true;
1091+
for (const [key, value] of Object.entries(content[eid])) {
1092+
if (!utils.isSupportedReceiptType(key)) continue;
1093+
if (!value) continue;
10941094

1095-
const readPrivate = content[eid][ReceiptType.ReadPrivate];
1096-
if (readPrivate && Object.keys(readPrivate).includes(this.getUserId())) return true;
1095+
if (Object.keys(value).includes(this.getUserId())) return true;
1096+
}
10971097

10981098
return false;
10991099
}).length > 0;
@@ -2583,7 +2583,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
25832583
public encryptAndSendToDevices(
25842584
userDeviceInfoArr: IOlmDevice<DeviceInfo>[],
25852585
payload: object,
2586-
): Promise<IEncryptAndSendToDevicesResult> {
2586+
): Promise<void> {
25872587
if (!this.crypto) {
25882588
throw new Error("End-to-End encryption disabled");
25892589
}
@@ -4661,7 +4661,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
46614661
room?.addLocalEchoReceipt(this.credentials.userId, rpEvent, ReceiptType.ReadPrivate);
46624662
}
46634663

4664-
return this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId);
4664+
return await this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId);
46654665
}
46664666

46674667
/**
@@ -7501,7 +7501,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
75017501
* don't want other users to see the read receipts. This is experimental. Optional.
75027502
* @return {Promise} Resolves: the empty object, {}.
75037503
*/
7504-
public setRoomReadMarkersHttpRequest(
7504+
public async setRoomReadMarkersHttpRequest(
75057505
roomId: string,
75067506
rmEventId: string,
75077507
rrEventId: string,
@@ -7514,9 +7514,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
75147514
const content = {
75157515
[ReceiptType.FullyRead]: rmEventId,
75167516
[ReceiptType.Read]: rrEventId,
7517-
[ReceiptType.ReadPrivate]: rpEventId,
75187517
};
75197518

7519+
const privateField = await utils.getPrivateReadReceiptField(this);
7520+
if (privateField) {
7521+
content[privateField] = rpEventId;
7522+
}
7523+
75207524
return this.http.authedRequest(undefined, Method.Post, path, undefined, content);
75217525
}
75227526

src/crypto/algorithms/megolm.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -606,19 +606,19 @@ class MegolmEncryption extends EncryptionAlgorithm {
606606
private encryptAndSendKeysToDevices(
607607
session: OutboundSessionInfo,
608608
chainIndex: number,
609-
userDeviceMap: IOlmDevice[],
609+
devices: IOlmDevice[],
610610
payload: IPayload,
611611
): Promise<void> {
612612
return this.crypto.encryptAndSendToDevices(
613-
userDeviceMap,
613+
devices,
614614
payload,
615-
).then(({ toDeviceBatch, deviceInfoByUserIdAndDeviceId }) => {
615+
).then(() => {
616616
// store that we successfully uploaded the keys of the current slice
617-
for (const msg of toDeviceBatch.batch) {
617+
for (const device of devices) {
618618
session.markSharedWithDevice(
619-
msg.userId,
620-
msg.deviceId,
621-
deviceInfoByUserIdAndDeviceId.get(msg.userId).get(msg.deviceId).getIdentityKey(),
619+
device.userId,
620+
device.deviceInfo.deviceId,
621+
device.deviceInfo.getIdentityKey(),
622622
chainIndex,
623623
);
624624
}

src/crypto/index.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,6 @@ export interface IEncryptedContent {
212212
}
213213
/* eslint-enable camelcase */
214214

215-
export interface IEncryptAndSendToDevicesResult {
216-
toDeviceBatch: ToDeviceBatch;
217-
deviceInfoByUserIdAndDeviceId: Map<string, Map<string, DeviceInfo>>;
218-
}
219-
220215
export enum CryptoEvent {
221216
DeviceVerificationChanged = "deviceVerificationChanged",
222217
UserTrustStatusChanged = "userTrustStatusChanged",
@@ -3129,12 +3124,11 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
31293124
public async encryptAndSendToDevices(
31303125
userDeviceInfoArr: IOlmDevice<DeviceInfo>[],
31313126
payload: object,
3132-
): Promise<IEncryptAndSendToDevicesResult> {
3127+
): Promise<void> {
31333128
const toDeviceBatch: ToDeviceBatch = {
31343129
eventType: EventType.RoomMessageEncrypted,
31353130
batch: [],
31363131
};
3137-
const deviceInfoByUserIdAndDeviceId = new Map<string, Map<string, DeviceInfo>>();
31383132

31393133
try {
31403134
await Promise.all(userDeviceInfoArr.map(async ({ userId, deviceInfo }) => {
@@ -3145,17 +3139,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
31453139
ciphertext: {},
31463140
};
31473141

3148-
// Assign to temp value to make type-checking happy
3149-
let userIdDeviceInfo = deviceInfoByUserIdAndDeviceId.get(userId);
3150-
3151-
if (userIdDeviceInfo === undefined) {
3152-
userIdDeviceInfo = new Map<string, DeviceInfo>();
3153-
deviceInfoByUserIdAndDeviceId.set(userId, userIdDeviceInfo);
3154-
}
3155-
3156-
// We hold by reference, this updates deviceInfoByUserIdAndDeviceId[userId]
3157-
userIdDeviceInfo.set(deviceId, deviceInfo);
3158-
31593142
toDeviceBatch.batch.push({
31603143
userId,
31613144
deviceId,
@@ -3193,7 +3176,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
31933176

31943177
try {
31953178
await this.baseApis.queueToDevice(toDeviceBatch);
3196-
return { toDeviceBatch, deviceInfoByUserIdAndDeviceId };
31973179
} catch (e) {
31983180
logger.error("sendToDevice failed", e);
31993181
throw e;

0 commit comments

Comments
 (0)