Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8711f15fd | |||
| 81f3aef960 | |||
| 4179f2978d | |||
| 97df6db49c | |||
| 3e693fab23 | |||
| 94f1eda830 | |||
| bc2a182ee9 | |||
| 39cf1863f1 |
+6
-9
@@ -1,11 +1,5 @@
|
||||
Changes in [23.1.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v23.1.0-rc.2) (2023-01-12)
|
||||
============================================================================================================
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Fix browser entrypoint ([\#3051](https://github.com/matrix-org/matrix-js-sdk/pull/3051)). Fixes #3013.
|
||||
|
||||
Changes in [23.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v23.1.0-rc.1) (2023-01-11)
|
||||
============================================================================================================
|
||||
Changes in [23.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v23.1.0) (2023-01-18)
|
||||
==================================================================================================
|
||||
|
||||
## 🦖 Deprecations
|
||||
* Remove extensible events v1 field population on legacy events ([\#3040](https://github.com/matrix-org/matrix-js-sdk/pull/3040)).
|
||||
@@ -27,10 +21,13 @@ Changes in [23.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/ta
|
||||
* Avoid triggering decryption errors when decrypting redacted events ([\#3004](https://github.com/matrix-org/matrix-js-sdk/pull/3004)). Fixes vector-im/element-web#24084.
|
||||
* bugfix: upload OTKs in sliding sync mode ([\#3008](https://github.com/matrix-org/matrix-js-sdk/pull/3008)).
|
||||
* Apply edits discovered from sync after thread is initialised ([\#3002](https://github.com/matrix-org/matrix-js-sdk/pull/3002)). Fixes vector-im/element-web#23921.
|
||||
* sliding sync: Fix issue where no unsubs are sent when switching rooms ([\#2991](https://github.com/matrix-org/matrix-js-sdk/pull/2991)).
|
||||
* Sliding sync: Fix issue where no unsubs are sent when switching rooms ([\#2991](https://github.com/matrix-org/matrix-js-sdk/pull/2991)).
|
||||
* Threads are missing from the timeline ([\#2996](https://github.com/matrix-org/matrix-js-sdk/pull/2996)). Fixes vector-im/element-web#24036.
|
||||
* Close all streams when a call ends ([\#2992](https://github.com/matrix-org/matrix-js-sdk/pull/2992)). Fixes vector-im/element-call#742.
|
||||
* Resume to-device message queue after resumed sync ([\#2920](https://github.com/matrix-org/matrix-js-sdk/pull/2920)). Fixes matrix-org/element-web-rageshakes#17170.
|
||||
* Fix browser entrypoint ([\#3051](https://github.com/matrix-org/matrix-js-sdk/pull/3051)). Fixes #3013.
|
||||
* Fix failure to start in firefox private browser ([\#3058](https://github.com/matrix-org/matrix-js-sdk/pull/3058)). Fixes vector-im/element-web#24216.
|
||||
* Correctly handle limited sync responses by resetting the thread timeline ([\#3056](https://github.com/matrix-org/matrix-js-sdk/pull/3056)). Fixes vector-im/element-web#23952.
|
||||
|
||||
Changes in [23.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v23.0.0) (2022-12-21)
|
||||
==================================================================================================
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "23.1.0-rc.2",
|
||||
"version": "23.1.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../../src/models/t
|
||||
import { mkThread } from "../../test-utils/thread";
|
||||
import { TestClient } from "../../TestClient";
|
||||
import { emitPromise, mkMessage, mock } from "../../test-utils/test-utils";
|
||||
import { EventStatus, MatrixEvent } from "../../../src";
|
||||
import { Direction, EventStatus, MatrixEvent } from "../../../src";
|
||||
import { ReceiptType } from "../../../src/@types/read_receipts";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client";
|
||||
import { ReEmitter } from "../../../src/ReEmitter";
|
||||
@@ -283,4 +283,143 @@ describe("Thread", () => {
|
||||
expect(thread2.getEventReadUpTo(myUserId)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resetLiveTimeline", () => {
|
||||
// ResetLiveTimeline is used when we have missing messages between the current live timeline's end and newly
|
||||
// received messages. In that case, we want to replace the existing live timeline. To ensure pagination
|
||||
// continues working correctly, new pagination tokens need to be set on both the old live timeline (which is
|
||||
// now a regular timeline) and the new live timeline.
|
||||
it("replaces the live timeline and correctly sets pagination tokens", async () => {
|
||||
const myUserId = "@bob:example.org";
|
||||
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
|
||||
timelineSupport: false,
|
||||
});
|
||||
const client = testClient.client;
|
||||
const room = new Room("123", client, myUserId, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
|
||||
const { thread } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: myUserId,
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 3,
|
||||
});
|
||||
await emitPromise(thread, ThreadEvent.Update);
|
||||
expect(thread.length).toBe(2);
|
||||
|
||||
jest.spyOn(client, "createMessagesRequest").mockImplementation((_, token) =>
|
||||
Promise.resolve({
|
||||
chunk: [],
|
||||
start: `${token}-new`,
|
||||
end: `${token}-new`,
|
||||
}),
|
||||
);
|
||||
|
||||
function timelines(): [string | null, string | null][] {
|
||||
return thread.timelineSet
|
||||
.getTimelines()
|
||||
.map((it) => [it.getPaginationToken(Direction.Backward), it.getPaginationToken(Direction.Forward)]);
|
||||
}
|
||||
|
||||
expect(timelines()).toEqual([[null, null]]);
|
||||
const promise = thread.resetLiveTimeline("b1", "f1");
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f1"],
|
||||
["b1", null],
|
||||
]);
|
||||
await promise;
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f1-new"],
|
||||
["b1-new", null],
|
||||
]);
|
||||
});
|
||||
|
||||
// As the pagination tokens cannot be used right now, resetLiveTimeline needs to replace them before they can
|
||||
// be used. But if in the future the bug in synapse is fixed, and they can actually be used, we can get into a
|
||||
// state where the client has paginated (and changed the tokens) while resetLiveTimeline tries to set the
|
||||
// corrected tokens. To prevent such a race condition, we make sure that resetLiveTimeline respects any
|
||||
// changes done to the pagination tokens.
|
||||
it("replaces the live timeline but does not replace changed pagination tokens", async () => {
|
||||
const myUserId = "@bob:example.org";
|
||||
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
|
||||
timelineSupport: false,
|
||||
});
|
||||
const client = testClient.client;
|
||||
const room = new Room("123", client, myUserId, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
|
||||
const { thread } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: myUserId,
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 3,
|
||||
});
|
||||
await emitPromise(thread, ThreadEvent.Update);
|
||||
expect(thread.length).toBe(2);
|
||||
|
||||
jest.spyOn(client, "createMessagesRequest").mockImplementation((_, token) =>
|
||||
Promise.resolve({
|
||||
chunk: [],
|
||||
start: `${token}-new`,
|
||||
end: `${token}-new`,
|
||||
}),
|
||||
);
|
||||
|
||||
function timelines(): [string | null, string | null][] {
|
||||
return thread.timelineSet
|
||||
.getTimelines()
|
||||
.map((it) => [it.getPaginationToken(Direction.Backward), it.getPaginationToken(Direction.Forward)]);
|
||||
}
|
||||
|
||||
expect(timelines()).toEqual([[null, null]]);
|
||||
const promise = thread.resetLiveTimeline("b1", "f1");
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f1"],
|
||||
["b1", null],
|
||||
]);
|
||||
thread.timelineSet.getTimelines()[0].setPaginationToken("f2", Direction.Forward);
|
||||
thread.timelineSet.getTimelines()[1].setPaginationToken("b2", Direction.Backward);
|
||||
await promise;
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f2"],
|
||||
["b2", null],
|
||||
]);
|
||||
});
|
||||
|
||||
it("is correctly called by the room", async () => {
|
||||
const myUserId = "@bob:example.org";
|
||||
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
|
||||
timelineSupport: false,
|
||||
});
|
||||
const client = testClient.client;
|
||||
const room = new Room("123", client, myUserId, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
|
||||
const { thread } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: myUserId,
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 3,
|
||||
});
|
||||
await emitPromise(thread, ThreadEvent.Update);
|
||||
expect(thread.length).toBe(2);
|
||||
const mock = jest.spyOn(thread, "resetLiveTimeline");
|
||||
mock.mockReturnValue(Promise.resolve());
|
||||
|
||||
room.resetLiveTimeline("b1", "f1");
|
||||
expect(mock).toHaveBeenCalledWith("b1", "f1");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+7
-3
@@ -1690,12 +1690,16 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
resolve(0);
|
||||
};
|
||||
req.onerror = (e): void => {
|
||||
logger.error(`Failed to remove IndexedDB instance ${dbname}: ${e}`);
|
||||
reject(new Error(`Error clearing storage: ${e}`));
|
||||
// In private browsing, Firefox has a global.indexedDB, but attempts to delete an indexeddb
|
||||
// (even a non-existent one) fail with "DOMException: A mutation operation was attempted on a
|
||||
// database that did not allow mutations."
|
||||
//
|
||||
// it seems like the only thing we can really do is ignore the error.
|
||||
logger.warn(`Failed to remove IndexedDB instance ${dbname}:`, e);
|
||||
resolve(0);
|
||||
};
|
||||
req.onblocked = (e): void => {
|
||||
logger.info(`cannot yet remove IndexedDB instance ${dbname}`);
|
||||
//reject(new Error(`Error clearing storage: ${e}`));
|
||||
};
|
||||
});
|
||||
await prom;
|
||||
|
||||
+4
-1
@@ -1114,6 +1114,9 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
for (const timelineSet of this.timelineSets) {
|
||||
timelineSet.resetLiveTimeline(backPaginationToken ?? undefined, forwardPaginationToken ?? undefined);
|
||||
}
|
||||
for (const thread of this.threads.values()) {
|
||||
thread.resetLiveTimeline(backPaginationToken, forwardPaginationToken);
|
||||
}
|
||||
|
||||
this.fixUpLegacyTimelineFields();
|
||||
}
|
||||
@@ -1223,7 +1226,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
const event = this.findEventById(eventId);
|
||||
const thread = this.findThreadForEvent(event);
|
||||
if (thread) {
|
||||
return thread.timelineSet.getLiveTimeline();
|
||||
return thread.timelineSet.getTimelineForEvent(eventId);
|
||||
} else {
|
||||
return this.getUnfilteredTimelineSet().getTimelineForEvent(eventId);
|
||||
}
|
||||
|
||||
+58
-1
@@ -256,7 +256,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.setEventMetadata(event);
|
||||
|
||||
const lastReply = this.lastReply();
|
||||
const isNewestReply = !lastReply || event.localTimestamp > lastReply!.localTimestamp;
|
||||
const isNewestReply = !lastReply || event.localTimestamp >= lastReply!.localTimestamp;
|
||||
|
||||
// Add all incoming events to the thread's timeline set when there's no server support
|
||||
if (!Thread.hasServerSideSupport) {
|
||||
@@ -358,6 +358,63 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.pendingReplyCount = pendingEvents.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the live timeline of all timelineSets, and start new ones.
|
||||
*
|
||||
* <p>This is used when /sync returns a 'limited' timeline. 'Limited' means that there's a gap between the messages
|
||||
* /sync returned, and the last known message in our timeline. In such a case, our live timeline isn't live anymore
|
||||
* and has to be replaced by a new one. To make sure we can continue paginating our timelines correctly, we have to
|
||||
* set new pagination tokens on the old and the new timeline.
|
||||
*
|
||||
* @param backPaginationToken - token for back-paginating the new timeline
|
||||
* @param forwardPaginationToken - token for forward-paginating the old live timeline,
|
||||
* if absent or null, all timelines are reset, removing old ones (including the previous live
|
||||
* timeline which would otherwise be unable to paginate forwards without this token).
|
||||
* Removing just the old live timeline whilst preserving previous ones is not supported.
|
||||
*/
|
||||
public async resetLiveTimeline(
|
||||
backPaginationToken?: string | null,
|
||||
forwardPaginationToken?: string | null,
|
||||
): Promise<void> {
|
||||
const oldLive = this.liveTimeline;
|
||||
this.timelineSet.resetLiveTimeline(backPaginationToken ?? undefined, forwardPaginationToken ?? undefined);
|
||||
const newLive = this.liveTimeline;
|
||||
|
||||
// FIXME: Remove the following as soon as https://github.com/matrix-org/synapse/issues/14830 is resolved.
|
||||
//
|
||||
// The pagination API for thread timelines currently can't handle the type of pagination tokens returned by sync
|
||||
//
|
||||
// To make this work anyway, we'll have to transform them into one of the types that the API can handle.
|
||||
// One option is passing the tokens to /messages, which can handle sync tokens, and returns the right format.
|
||||
// /messages does not return new tokens on requests with a limit of 0.
|
||||
// This means our timelines might overlap a slight bit, but that's not an issue, as we deduplicate messages
|
||||
// anyway.
|
||||
|
||||
let newBackward: string | undefined;
|
||||
let oldForward: string | undefined;
|
||||
if (backPaginationToken) {
|
||||
const res = await this.client.createMessagesRequest(this.roomId, backPaginationToken, 1, Direction.Forward);
|
||||
newBackward = res.end;
|
||||
}
|
||||
if (forwardPaginationToken) {
|
||||
const res = await this.client.createMessagesRequest(
|
||||
this.roomId,
|
||||
forwardPaginationToken,
|
||||
1,
|
||||
Direction.Backward,
|
||||
);
|
||||
oldForward = res.start;
|
||||
}
|
||||
// Only replace the token if we don't have paginated away from this position already. This situation doesn't
|
||||
// occur today, but if the above issue is resolved, we'd have to go down this path.
|
||||
if (forwardPaginationToken && oldLive.getPaginationToken(Direction.Forward) === forwardPaginationToken) {
|
||||
oldLive.setPaginationToken(oldForward ?? null, Direction.Forward);
|
||||
}
|
||||
if (backPaginationToken && newLive.getPaginationToken(Direction.Backward) === backPaginationToken) {
|
||||
newLive.setPaginationToken(newBackward ?? null, Direction.Backward);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateThreadMetadata(): Promise<void> {
|
||||
this.updatePendingReplyCount();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user