You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-30 04:23:07 +03:00
Sync knock rooms (#3703)
Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net> Co-authored-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
This commit is contained in:
@ -223,6 +223,121 @@ describe("MatrixClient syncing", () => {
|
|||||||
expect(fires).toBe(3);
|
expect(fires).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should emit RoomEvent.MyMembership for knock->leave->knock cycles", async () => {
|
||||||
|
await client!.initCrypto();
|
||||||
|
|
||||||
|
const roomId = "!cycles:example.org";
|
||||||
|
|
||||||
|
// First sync: an knock
|
||||||
|
const knockSyncRoomSection = {
|
||||||
|
knock: {
|
||||||
|
[roomId]: {
|
||||||
|
knock_state: {
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: "m.room.member",
|
||||||
|
state_key: selfUserId,
|
||||||
|
content: {
|
||||||
|
membership: "knock",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
httpBackend!.when("GET", "/sync").respond(200, {
|
||||||
|
...syncData,
|
||||||
|
rooms: knockSyncRoomSection,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second sync: a leave (reject of some kind)
|
||||||
|
httpBackend!.when("POST", "/leave").respond(200, {});
|
||||||
|
httpBackend!.when("GET", "/sync").respond(200, {
|
||||||
|
...syncData,
|
||||||
|
rooms: {
|
||||||
|
leave: {
|
||||||
|
[roomId]: {
|
||||||
|
account_data: { events: [] },
|
||||||
|
ephemeral: { events: [] },
|
||||||
|
state: {
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: "m.room.member",
|
||||||
|
state_key: selfUserId,
|
||||||
|
content: {
|
||||||
|
membership: "leave",
|
||||||
|
},
|
||||||
|
prev_content: {
|
||||||
|
membership: "knock",
|
||||||
|
},
|
||||||
|
// XXX: And other fields required on an event
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
timeline: {
|
||||||
|
limited: false,
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: "m.room.member",
|
||||||
|
state_key: selfUserId,
|
||||||
|
content: {
|
||||||
|
membership: "leave",
|
||||||
|
},
|
||||||
|
prev_content: {
|
||||||
|
membership: "knock",
|
||||||
|
},
|
||||||
|
// XXX: And other fields required on an event
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Third sync: another knock
|
||||||
|
httpBackend!.when("GET", "/sync").respond(200, {
|
||||||
|
...syncData,
|
||||||
|
rooms: knockSyncRoomSection,
|
||||||
|
});
|
||||||
|
|
||||||
|
// First fire: an initial knock
|
||||||
|
let fires = 0;
|
||||||
|
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
|
||||||
|
// Room, string, string
|
||||||
|
fires++;
|
||||||
|
expect(room.roomId).toBe(roomId);
|
||||||
|
expect(membership).toBe("knock");
|
||||||
|
expect(oldMembership).toBeFalsy();
|
||||||
|
|
||||||
|
// Second fire: a leave
|
||||||
|
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
|
||||||
|
fires++;
|
||||||
|
expect(room.roomId).toBe(roomId);
|
||||||
|
expect(membership).toBe("leave");
|
||||||
|
expect(oldMembership).toBe("knock");
|
||||||
|
|
||||||
|
// Third/final fire: a second knock
|
||||||
|
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
|
||||||
|
fires++;
|
||||||
|
expect(room.roomId).toBe(roomId);
|
||||||
|
expect(membership).toBe("knock");
|
||||||
|
expect(oldMembership).toBe("leave");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// For maximum safety, "leave" the room after we register the handler
|
||||||
|
client!.leave(roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
|
client!.startClient();
|
||||||
|
await httpBackend!.flushAllExpected();
|
||||||
|
|
||||||
|
expect(fires).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
it("should honour lazyLoadMembers if user is not a guest", () => {
|
it("should honour lazyLoadMembers if user is not a guest", () => {
|
||||||
httpBackend!
|
httpBackend!
|
||||||
.when("GET", "/sync")
|
.when("GET", "/sync")
|
||||||
@ -293,6 +408,46 @@ describe("MatrixClient syncing", () => {
|
|||||||
expect(fires).toBe(1);
|
expect(fires).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should emit ClientEvent.Room when knocked while crypto is disabled", async () => {
|
||||||
|
const roomId = "!knock:example.org";
|
||||||
|
|
||||||
|
// First sync: a knock
|
||||||
|
const knockSyncRoomSection = {
|
||||||
|
knock: {
|
||||||
|
[roomId]: {
|
||||||
|
knock_state: {
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: "m.room.member",
|
||||||
|
state_key: selfUserId,
|
||||||
|
content: {
|
||||||
|
membership: "knock",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
httpBackend!.when("GET", "/sync").respond(200, {
|
||||||
|
...syncData,
|
||||||
|
rooms: knockSyncRoomSection,
|
||||||
|
});
|
||||||
|
|
||||||
|
// First fire: an initial knock
|
||||||
|
let fires = 0;
|
||||||
|
client!.once(ClientEvent.Room, (room) => {
|
||||||
|
fires++;
|
||||||
|
expect(room.roomId).toBe(roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
|
client!.startClient();
|
||||||
|
await httpBackend!.flushAllExpected();
|
||||||
|
|
||||||
|
expect(fires).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("should work when all network calls fail", async () => {
|
it("should work when all network calls fail", async () => {
|
||||||
httpBackend!.expectedRequests = [];
|
httpBackend!.expectedRequests = [];
|
||||||
httpBackend!.when("GET", "").fail(0, new Error("CORS or something"));
|
httpBackend!.when("GET", "").fail(0, new Error("CORS or something"));
|
||||||
@ -358,6 +513,7 @@ describe("MatrixClient syncing", () => {
|
|||||||
join: {},
|
join: {},
|
||||||
invite: {},
|
invite: {},
|
||||||
leave: {},
|
leave: {},
|
||||||
|
knock: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -376,6 +376,7 @@ describe("MatrixClient syncing", () => {
|
|||||||
},
|
},
|
||||||
[Category.Leave]: {},
|
[Category.Leave]: {},
|
||||||
[Category.Invite]: {},
|
[Category.Invite]: {},
|
||||||
|
[Category.Knock]: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ export function getSyncResponse(roomMembers: string[], roomId = TEST_ROOM_ID): I
|
|||||||
join: { [roomId]: roomResponse },
|
join: { [roomId]: roomResponse },
|
||||||
invite: {},
|
invite: {},
|
||||||
leave: {},
|
leave: {},
|
||||||
|
knock: {},
|
||||||
},
|
},
|
||||||
account_data: { events: [] },
|
account_data: { events: [] },
|
||||||
};
|
};
|
||||||
|
@ -16,8 +16,9 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReceiptType } from "../../src/@types/read_receipts";
|
import { ReceiptType } from "../../src/@types/read_receipts";
|
||||||
import { IJoinedRoom, ISyncResponse, SyncAccumulator } from "../../src/sync-accumulator";
|
import { IJoinedRoom, IKnockedRoom, IStrippedState, ISyncResponse, SyncAccumulator } from "../../src/sync-accumulator";
|
||||||
import { IRoomSummary } from "../../src";
|
import { IRoomSummary } from "../../src";
|
||||||
|
import * as utils from "../test-utils/test-utils";
|
||||||
|
|
||||||
// The event body & unsigned object get frozen to assert that they don't get altered
|
// The event body & unsigned object get frozen to assert that they don't get altered
|
||||||
// by the impl
|
// by the impl
|
||||||
@ -95,6 +96,13 @@ describe("SyncAccumulator", function () {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
knock: {
|
||||||
|
"!knock": {
|
||||||
|
knock_state: {
|
||||||
|
events: [member("alice", "knock")],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as unknown as ISyncResponse;
|
} as unknown as ISyncResponse;
|
||||||
sa.accumulate(res);
|
sa.accumulate(res);
|
||||||
@ -287,6 +295,71 @@ describe("SyncAccumulator", function () {
|
|||||||
expect(sa.getJSON().accountData[0]).toEqual(acc2);
|
expect(sa.getJSON().accountData[0]).toEqual(acc2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should accumulate knock state", () => {
|
||||||
|
const initKnockState = {
|
||||||
|
events: [member("alice", "knock")],
|
||||||
|
};
|
||||||
|
sa.accumulate(
|
||||||
|
syncSkeleton(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
knock_state: initKnockState,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(sa.getJSON().roomsData.knock["!knock:bar"].knock_state).toBe(initKnockState);
|
||||||
|
|
||||||
|
sa.accumulate(
|
||||||
|
syncSkeleton(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
knock_state: {
|
||||||
|
events: [
|
||||||
|
utils.mkEvent({
|
||||||
|
user: "alice",
|
||||||
|
room: "!knock:bar",
|
||||||
|
type: "m.room.name",
|
||||||
|
content: {
|
||||||
|
name: "Room 1",
|
||||||
|
},
|
||||||
|
}) as IStrippedState,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sa.getJSON().roomsData.knock["!knock:bar"].knock_state.events.find((e) => e.type === "m.room.name")?.content
|
||||||
|
.name,
|
||||||
|
).toEqual("Room 1");
|
||||||
|
|
||||||
|
sa.accumulate(
|
||||||
|
syncSkeleton(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
knock_state: {
|
||||||
|
events: [
|
||||||
|
utils.mkEvent({
|
||||||
|
user: "alice",
|
||||||
|
room: "!knock:bar",
|
||||||
|
type: "m.room.name",
|
||||||
|
content: {
|
||||||
|
name: "Room 2",
|
||||||
|
},
|
||||||
|
}) as IStrippedState,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sa.getJSON().roomsData.knock["!knock:bar"].knock_state.events.find((e) => e.type === "m.room.name")?.content
|
||||||
|
.name,
|
||||||
|
).toEqual("Room 2");
|
||||||
|
});
|
||||||
|
|
||||||
it("should accumulate read receipts", () => {
|
it("should accumulate read receipts", () => {
|
||||||
const receipt1 = {
|
const receipt1 = {
|
||||||
type: "m.receipt",
|
type: "m.receipt",
|
||||||
@ -601,7 +674,7 @@ describe("SyncAccumulator", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function syncSkeleton(joinObj: Partial<IJoinedRoom>): ISyncResponse {
|
function syncSkeleton(joinObj: Partial<IJoinedRoom>, knockObj?: Partial<IKnockedRoom>): ISyncResponse {
|
||||||
joinObj = joinObj || {};
|
joinObj = joinObj || {};
|
||||||
return {
|
return {
|
||||||
next_batch: "abc",
|
next_batch: "abc",
|
||||||
@ -609,6 +682,11 @@ function syncSkeleton(joinObj: Partial<IJoinedRoom>): ISyncResponse {
|
|||||||
join: {
|
join: {
|
||||||
"!foo:bar": joinObj,
|
"!foo:bar": joinObj,
|
||||||
},
|
},
|
||||||
|
knock: knockObj
|
||||||
|
? {
|
||||||
|
"!knock:bar": knockObj,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
} as unknown as ISyncResponse;
|
} as unknown as ISyncResponse;
|
||||||
}
|
}
|
||||||
|
@ -849,7 +849,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns the membership type (join | leave | invite) for the logged in user
|
* @returns the membership type (join | leave | invite | knock) for the logged in user
|
||||||
*/
|
*/
|
||||||
public getMyMembership(): string {
|
public getMyMembership(): string {
|
||||||
return this.selfMembership ?? "leave";
|
return this.selfMembership ?? "leave";
|
||||||
|
@ -99,6 +99,10 @@ export interface IInviteState {
|
|||||||
events: IStrippedState[];
|
events: IStrippedState[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IKnockState {
|
||||||
|
events: IStrippedState[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IInvitedRoom {
|
export interface IInvitedRoom {
|
||||||
invite_state: IInviteState;
|
invite_state: IInviteState;
|
||||||
}
|
}
|
||||||
@ -109,10 +113,15 @@ export interface ILeftRoom {
|
|||||||
account_data: IAccountData;
|
account_data: IAccountData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IKnockedRoom {
|
||||||
|
knock_state: IKnockState;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IRooms {
|
export interface IRooms {
|
||||||
[Category.Join]: Record<string, IJoinedRoom>;
|
[Category.Join]: Record<string, IJoinedRoom>;
|
||||||
[Category.Invite]: Record<string, IInvitedRoom>;
|
[Category.Invite]: Record<string, IInvitedRoom>;
|
||||||
[Category.Leave]: Record<string, ILeftRoom>;
|
[Category.Leave]: Record<string, ILeftRoom>;
|
||||||
|
[Category.Knock]: Record<string, IKnockedRoom>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPresence {
|
interface IPresence {
|
||||||
@ -156,6 +165,7 @@ export enum Category {
|
|||||||
Invite = "invite",
|
Invite = "invite",
|
||||||
Leave = "leave",
|
Leave = "leave",
|
||||||
Join = "join",
|
Join = "join",
|
||||||
|
Knock = "knock",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoom {
|
interface IRoom {
|
||||||
@ -196,6 +206,7 @@ function isTaggedEvent(event: IRoomEvent): event is TaggedEvent {
|
|||||||
export class SyncAccumulator {
|
export class SyncAccumulator {
|
||||||
private accountData: Record<string, IMinimalEvent> = {}; // $event_type: Object
|
private accountData: Record<string, IMinimalEvent> = {}; // $event_type: Object
|
||||||
private inviteRooms: Record<string, IInvitedRoom> = {}; // $roomId: { ... sync 'invite' json data ... }
|
private inviteRooms: Record<string, IInvitedRoom> = {}; // $roomId: { ... sync 'invite' json data ... }
|
||||||
|
private knockRooms: Record<string, IKnockedRoom> = {}; // $roomId: { ... sync 'knock' json data ... }
|
||||||
private joinRooms: { [roomId: string]: IRoom } = {};
|
private joinRooms: { [roomId: string]: IRoom } = {};
|
||||||
// the /sync token which corresponds to the last time rooms were
|
// the /sync token which corresponds to the last time rooms were
|
||||||
// accumulated. We remember this so that any caller can obtain a
|
// accumulated. We remember this so that any caller can obtain a
|
||||||
@ -247,11 +258,17 @@ export class SyncAccumulator {
|
|||||||
this.accumulateRoom(roomId, Category.Leave, syncResponse.rooms.leave[roomId], fromDatabase);
|
this.accumulateRoom(roomId, Category.Leave, syncResponse.rooms.leave[roomId], fromDatabase);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (syncResponse.rooms.knock) {
|
||||||
|
Object.keys(syncResponse.rooms.knock).forEach((roomId) => {
|
||||||
|
this.accumulateRoom(roomId, Category.Knock, syncResponse.rooms.knock[roomId], fromDatabase);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private accumulateRoom(roomId: string, category: Category.Invite, data: IInvitedRoom, fromDatabase: boolean): void;
|
private accumulateRoom(roomId: string, category: Category.Invite, data: IInvitedRoom, fromDatabase: boolean): void;
|
||||||
private accumulateRoom(roomId: string, category: Category.Join, data: IJoinedRoom, fromDatabase: boolean): void;
|
private accumulateRoom(roomId: string, category: Category.Join, data: IJoinedRoom, fromDatabase: boolean): void;
|
||||||
private accumulateRoom(roomId: string, category: Category.Leave, data: ILeftRoom, fromDatabase: boolean): void;
|
private accumulateRoom(roomId: string, category: Category.Leave, data: ILeftRoom, fromDatabase: boolean): void;
|
||||||
|
private accumulateRoom(roomId: string, category: Category.Knock, data: IKnockedRoom, fromDatabase: boolean): void;
|
||||||
private accumulateRoom(roomId: string, category: Category, data: any, fromDatabase = false): void {
|
private accumulateRoom(roomId: string, category: Category, data: any, fromDatabase = false): void {
|
||||||
// Valid /sync state transitions
|
// Valid /sync state transitions
|
||||||
// +--------+ <======+ 1: Accept an invite
|
// +--------+ <======+ 1: Accept an invite
|
||||||
@ -269,6 +286,10 @@ export class SyncAccumulator {
|
|||||||
this.accumulateInviteState(roomId, data as IInvitedRoom);
|
this.accumulateInviteState(roomId, data as IInvitedRoom);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Category.Knock:
|
||||||
|
this.accumulateKnockState(roomId, data as IKnockedRoom);
|
||||||
|
break;
|
||||||
|
|
||||||
case Category.Join:
|
case Category.Join:
|
||||||
if (this.inviteRooms[roomId]) {
|
if (this.inviteRooms[roomId]) {
|
||||||
// (1)
|
// (1)
|
||||||
@ -326,6 +347,36 @@ export class SyncAccumulator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private accumulateKnockState(roomId: string, data: IKnockedRoom): void {
|
||||||
|
if (!data.knock_state || !data.knock_state.events) {
|
||||||
|
// no new data
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.knockRooms[roomId]) {
|
||||||
|
this.knockRooms[roomId] = {
|
||||||
|
knock_state: data.knock_state,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// accumulate extra keys
|
||||||
|
// clobber based on event type / state key
|
||||||
|
// We expect knock_state to be small, so just loop over the events
|
||||||
|
const currentData = this.knockRooms[roomId];
|
||||||
|
data.knock_state.events.forEach((e) => {
|
||||||
|
let hasAdded = false;
|
||||||
|
for (let i = 0; i < currentData.knock_state.events.length; i++) {
|
||||||
|
const current = currentData.knock_state.events[i];
|
||||||
|
if (current.type === e.type && current.state_key == e.state_key) {
|
||||||
|
currentData.knock_state.events[i] = e; // update
|
||||||
|
hasAdded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasAdded) {
|
||||||
|
currentData.knock_state.events.push(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Accumulate timeline and state events in a room.
|
// Accumulate timeline and state events in a room.
|
||||||
private accumulateJoinState(roomId: string, data: IJoinedRoom, fromDatabase = false): void {
|
private accumulateJoinState(roomId: string, data: IJoinedRoom, fromDatabase = false): void {
|
||||||
// We expect this function to be called a lot (every /sync) so we want
|
// We expect this function to be called a lot (every /sync) so we want
|
||||||
@ -485,6 +536,7 @@ export class SyncAccumulator {
|
|||||||
const data: IRooms = {
|
const data: IRooms = {
|
||||||
join: {},
|
join: {},
|
||||||
invite: {},
|
invite: {},
|
||||||
|
knock: {},
|
||||||
// always empty. This is set by /sync when a room was previously
|
// always empty. This is set by /sync when a room was previously
|
||||||
// in 'invite' or 'join'. On fresh startup, the client won't know
|
// in 'invite' or 'join'. On fresh startup, the client won't know
|
||||||
// about any previous room being in 'invite' or 'join' so we can
|
// about any previous room being in 'invite' or 'join' so we can
|
||||||
@ -501,6 +553,9 @@ export class SyncAccumulator {
|
|||||||
Object.keys(this.inviteRooms).forEach((roomId) => {
|
Object.keys(this.inviteRooms).forEach((roomId) => {
|
||||||
data.invite[roomId] = this.inviteRooms[roomId];
|
data.invite[roomId] = this.inviteRooms[roomId];
|
||||||
});
|
});
|
||||||
|
Object.keys(this.knockRooms).forEach((roomId) => {
|
||||||
|
data.knock[roomId] = this.knockRooms[roomId];
|
||||||
|
});
|
||||||
Object.keys(this.joinRooms).forEach((roomId) => {
|
Object.keys(this.joinRooms).forEach((roomId) => {
|
||||||
const roomData = this.joinRooms[roomId];
|
const roomData = this.joinRooms[roomId];
|
||||||
const roomJson: IJoinedRoom = {
|
const roomJson: IJoinedRoom = {
|
||||||
|
27
src/sync.ts
27
src/sync.ts
@ -40,6 +40,7 @@ import {
|
|||||||
IInviteState,
|
IInviteState,
|
||||||
IJoinedRoom,
|
IJoinedRoom,
|
||||||
ILeftRoom,
|
ILeftRoom,
|
||||||
|
IKnockedRoom,
|
||||||
IMinimalEvent,
|
IMinimalEvent,
|
||||||
IRoomEvent,
|
IRoomEvent,
|
||||||
IStateEvent,
|
IStateEvent,
|
||||||
@ -1240,6 +1241,7 @@ export class SyncApi {
|
|||||||
let inviteRooms: WrappedRoom<IInvitedRoom>[] = [];
|
let inviteRooms: WrappedRoom<IInvitedRoom>[] = [];
|
||||||
let joinRooms: WrappedRoom<IJoinedRoom>[] = [];
|
let joinRooms: WrappedRoom<IJoinedRoom>[] = [];
|
||||||
let leaveRooms: WrappedRoom<ILeftRoom>[] = [];
|
let leaveRooms: WrappedRoom<ILeftRoom>[] = [];
|
||||||
|
let knockRooms: WrappedRoom<IKnockedRoom>[] = [];
|
||||||
|
|
||||||
if (data.rooms) {
|
if (data.rooms) {
|
||||||
if (data.rooms.invite) {
|
if (data.rooms.invite) {
|
||||||
@ -1251,6 +1253,9 @@ export class SyncApi {
|
|||||||
if (data.rooms.leave) {
|
if (data.rooms.leave) {
|
||||||
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
|
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
|
||||||
}
|
}
|
||||||
|
if (data.rooms.knock) {
|
||||||
|
knockRooms = this.mapSyncResponseToRoomArray(data.rooms.knock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notifEvents = [];
|
this.notifEvents = [];
|
||||||
@ -1511,6 +1516,26 @@ export class SyncApi {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle knocks
|
||||||
|
await promiseMapSeries(knockRooms, async (knockObj) => {
|
||||||
|
const room = knockObj.room;
|
||||||
|
const stateEvents = this.mapSyncEventsFormat(knockObj.knock_state, room);
|
||||||
|
|
||||||
|
await this.injectRoomEvents(room, stateEvents);
|
||||||
|
|
||||||
|
if (knockObj.isBrandNewRoom) {
|
||||||
|
room.recalculate();
|
||||||
|
client.store.storeRoom(room);
|
||||||
|
client.emit(ClientEvent.Room, room);
|
||||||
|
} else {
|
||||||
|
// Update room state for knock->leave->knock cycles
|
||||||
|
room.recalculate();
|
||||||
|
}
|
||||||
|
stateEvents.forEach(function (e) {
|
||||||
|
client.emit(ClientEvent.Event, e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// update the notification timeline, if appropriate.
|
// update the notification timeline, if appropriate.
|
||||||
// we only do this for live events, as otherwise we can't order them sanely
|
// we only do this for live events, as otherwise we can't order them sanely
|
||||||
// in the timeline relative to ones paginated in by /notifications.
|
// in the timeline relative to ones paginated in by /notifications.
|
||||||
@ -1629,7 +1654,7 @@ export class SyncApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapSyncResponseToRoomArray<T extends ILeftRoom | IJoinedRoom | IInvitedRoom>(
|
private mapSyncResponseToRoomArray<T extends ILeftRoom | IJoinedRoom | IInvitedRoom | IKnockedRoom>(
|
||||||
obj: Record<string, T>,
|
obj: Record<string, T>,
|
||||||
): Array<WrappedRoom<T>> {
|
): Array<WrappedRoom<T>> {
|
||||||
// Maps { roomid: {stuff}, roomid: {stuff} }
|
// Maps { roomid: {stuff}, roomid: {stuff} }
|
||||||
|
Reference in New Issue
Block a user