1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

Convert Sync and SyncAccumulator to Typescript

This commit is contained in:
Michael Telatynski
2021-07-01 22:47:50 +01:00
parent a99c1e96d6
commit cab334ed73
12 changed files with 1981 additions and 1820 deletions

View File

@@ -298,6 +298,11 @@ export interface IMatrixClientCreateOpts extends ICreateClientOpts {
usingExternalCrypto?: boolean; usingExternalCrypto?: boolean;
} }
export enum PendingEventOrdering {
Chronological = "chronological",
Detached = "detached",
}
export interface IStartClientOpts { export interface IStartClientOpts {
/** /**
* The event <code>limit=</code> to apply to initial sync. Default: 8. * The event <code>limit=</code> to apply to initial sync. Default: 8.
@@ -320,7 +325,7 @@ export interface IStartClientOpts {
* pending messages will appear in a separate list, accessbile via {@link module:models/room#getPendingEvents}. * pending messages will appear in a separate list, accessbile via {@link module:models/room#getPendingEvents}.
* Default: "chronological". * Default: "chronological".
*/ */
pendingEventOrdering?: "chronological" | "detached"; pendingEventOrdering?: PendingEventOrdering;
/** /**
* The number of milliseconds to wait on /sync. Default: 30000 (30 seconds). * The number of milliseconds to wait on /sync. Default: 30000 (30 seconds).
@@ -454,7 +459,7 @@ export class MatrixClient extends EventEmitter {
protected fallbackICEServerAllowed = false; protected fallbackICEServerAllowed = false;
protected roomList: RoomList; protected roomList: RoomList;
protected syncApi: SyncApi; protected syncApi: SyncApi;
protected pushRules: any; // TODO: Types public pushRules: any; // TODO: Types
protected syncLeftRoomsPromise: Promise<Room[]>; protected syncLeftRoomsPromise: Promise<Room[]>;
protected syncedLeftRooms = false; protected syncedLeftRooms = false;
protected clientOpts: IStoredClientOpts; protected clientOpts: IStoredClientOpts;

View File

@@ -57,6 +57,7 @@ import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base
import type { RoomList } from "./RoomList"; import type { RoomList } from "./RoomList";
import { IRecoveryKey, IEncryptedEventInfo } from "./api"; import { IRecoveryKey, IEncryptedEventInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
import { ISyncStateData } from "../sync";
const DeviceVerification = DeviceInfo.DeviceVerification; const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -148,12 +149,6 @@ interface IUserOlmSession {
}[]; }[];
} }
interface ISyncData {
oldSyncToken?: string;
nextSyncToken: string;
catchingUp?: boolean;
}
interface ISyncDeviceLists { interface ISyncDeviceLists {
changed: string[]; changed: string[];
left: string[]; left: string[];
@@ -2780,7 +2775,7 @@ export class Crypto extends EventEmitter {
* @param {Object} syncDeviceLists device_lists field from /sync, or response from * @param {Object} syncDeviceLists device_lists field from /sync, or response from
* /keys/changes * /keys/changes
*/ */
public async handleDeviceListChanges(syncData: ISyncData, syncDeviceLists: ISyncDeviceLists): Promise<void> { public async handleDeviceListChanges(syncData: ISyncStateData, syncDeviceLists: ISyncDeviceLists): Promise<void> {
// Initial syncs don't have device change lists. We'll either get the complete list // Initial syncs don't have device change lists. We'll either get the complete list
// of changes for the interval or will have invalidated everything in willProcessSync // of changes for the interval or will have invalidated everything in willProcessSync
if (!syncData.oldSyncToken) return; if (!syncData.oldSyncToken) return;
@@ -2870,7 +2865,7 @@ export class Crypto extends EventEmitter {
* *
* @param {Object} syncData the data from the 'MatrixClient.sync' event * @param {Object} syncData the data from the 'MatrixClient.sync' event
*/ */
public async onSyncWillProcess(syncData: ISyncData): Promise<void> { public async onSyncWillProcess(syncData: ISyncStateData): Promise<void> {
if (!syncData.oldSyncToken) { if (!syncData.oldSyncToken) {
// If there is no old sync token, we start all our tracking from // If there is no old sync token, we start all our tracking from
// scratch, so mark everything as untracked. onCryptoEvent will // scratch, so mark everything as untracked. onCryptoEvent will
@@ -2894,7 +2889,7 @@ export class Crypto extends EventEmitter {
* *
* @param {Object} syncData the data from the 'MatrixClient.sync' event * @param {Object} syncData the data from the 'MatrixClient.sync' event
*/ */
public async onSyncCompleted(syncData: ISyncData): Promise<void> { public async onSyncCompleted(syncData: ISyncStateData): Promise<void> {
this.deviceList.setSyncToken(syncData.nextSyncToken); this.deviceList.setSyncToken(syncData.nextSyncToken);
this.deviceList.saveIfDirty(); this.deviceList.saveIfDirty();

View File

@@ -74,7 +74,7 @@ export interface IContent {
type StrippedState = Required<Pick<IEvent, "content" | "state_key" | "type" | "sender">>; type StrippedState = Required<Pick<IEvent, "content" | "state_key" | "type" | "sender">>;
interface IUnsigned { export interface IUnsigned {
age?: number; age?: number;
prev_sender?: string; prev_sender?: string;
prev_content?: IContent; prev_content?: IContent;

View File

@@ -29,6 +29,7 @@ import { RoomState } from "./room-state";
export class RoomMember extends EventEmitter { export class RoomMember extends EventEmitter {
private _isOutOfBand = false; private _isOutOfBand = false;
private _modified: number; private _modified: number;
public _requestedProfileInfo: boolean; // used by sync.ts
// XXX these should be read-only // XXX these should be read-only
public typing = false; public typing = false;

View File

@@ -21,6 +21,14 @@ import { User } from "../models/user";
import { MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { Filter } from "../filter"; import { Filter } from "../filter";
import { RoomSummary } from "../models/room-summary"; import { RoomSummary } from "../models/room-summary";
import { IMinimalEvent, IGroups, IRooms } from "../sync-accumulator";
export interface ISavedSync {
nextBatch: string;
roomsData: IRooms;
groupsData: IGroups;
accountData: IMinimalEvent[];
}
/** /**
* Construct a stub store. This does no-ops on most store methods. * Construct a stub store. This does no-ops on most store methods.
@@ -199,7 +207,7 @@ export interface IStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
getSavedSync(): Promise<object>; getSavedSync(): Promise<ISavedSync>;
/** /**
* @return {Promise} If there is a saved sync, the nextBatch token * @return {Promise} If there is a saved sync, the nextBatch token

View File

@@ -24,6 +24,7 @@ import { RemoteIndexedDBStoreBackend } from "./indexeddb-remote-backend.js";
import { User } from "../models/user"; import { User } from "../models/user";
import { MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { logger } from '../logger'; import { logger } from '../logger';
import { ISavedSync } from "./index";
/** /**
* This is an internal module. See {@link IndexedDBStore} for the public class. * This is an internal module. See {@link IndexedDBStore} for the public class.
@@ -157,7 +158,7 @@ export class IndexedDBStore extends MemoryStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
public getSavedSync = this.degradable((): Promise<object> => { public getSavedSync = this.degradable((): Promise<ISavedSync> => {
return this.backend.getSavedSync(); return this.backend.getSavedSync();
}, "getSavedSync"); }, "getSavedSync");

View File

@@ -27,7 +27,7 @@ import { MatrixEvent } from "../models/event";
import { RoomState } from "../models/room-state"; import { RoomState } from "../models/room-state";
import { RoomMember } from "../models/room-member"; import { RoomMember } from "../models/room-member";
import { Filter } from "../filter"; import { Filter } from "../filter";
import { IStore } from "./index"; import { ISavedSync, IStore } from "./index";
import { RoomSummary } from "../models/room-summary"; import { RoomSummary } from "../models/room-summary";
function isValidFilterId(filterId: string): boolean { function isValidFilterId(filterId: string): boolean {
@@ -373,7 +373,7 @@ export class MemoryStore implements IStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
public getSavedSync(): Promise<object> { public getSavedSync(): Promise<ISavedSync> {
return Promise.resolve(null); return Promise.resolve(null);
} }

View File

@@ -25,7 +25,7 @@ import { Room } from "../models/room";
import { User } from "../models/user"; import { User } from "../models/user";
import { MatrixEvent } from "../models/event"; import { MatrixEvent } from "../models/event";
import { Filter } from "../filter"; import { Filter } from "../filter";
import { IStore } from "./index"; import { ISavedSync, IStore } from "./index";
import { RoomSummary } from "../models/room-summary"; import { RoomSummary } from "../models/room-summary";
/** /**
@@ -243,7 +243,7 @@ export class StubStore implements IStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
public getSavedSync(): Promise<object> { public getSavedSync(): Promise<ISavedSync> {
return Promise.resolve(null); return Promise.resolve(null);
} }

View File

@@ -1,7 +1,5 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -23,6 +21,153 @@ limitations under the License.
import { logger } from './logger'; import { logger } from './logger';
import { deepCopy } from "./utils"; import { deepCopy } from "./utils";
import { IContent, IUnsigned } from "./models/event";
import { IRoomSummary } from "./models/room-summary";
import { EventType } from "./@types/event";
interface IOpts {
maxTimelineEntries?: number;
}
export interface IMinimalEvent {
content: IContent;
type: EventType | string;
}
export interface IEphemeral {
events: IMinimalEvent[];
}
/* eslint-disable camelcase */
interface IUnreadNotificationCounts {
highlight_count: number;
notification_count: number;
}
export interface IRoomEvent extends IMinimalEvent {
event_id: string;
sender: string;
origin_server_ts: number;
unsigned?: IUnsigned;
/** @deprecated - legacy field */
age?: number;
}
export interface IStateEvent extends IRoomEvent {
prev_content?: IContent;
state_key: string;
}
interface IState {
events: IStateEvent[];
}
export interface ITimeline {
events: Array<IRoomEvent | IStateEvent>;
limited: boolean;
prev_batch: string;
}
export interface IJoinedRoom {
summary: IRoomSummary;
state: IState;
timeline: ITimeline;
ephemeral: IEphemeral;
account_data: IAccountData;
unread_notifications: IUnreadNotificationCounts;
}
export interface IStrippedState {
content: IContent;
state_key: string;
type: EventType | string;
sender: string;
}
export interface IInviteState {
events: IStrippedState[];
}
export interface IInvitedRoom {
invite_state: IInviteState;
}
export interface ILeftRoom {
state: IState;
timeline: ITimeline;
account_data: IAccountData;
}
export interface IRooms {
[Category.Join]: Record<string, IJoinedRoom>;
[Category.Invite]: Record<string, IInvitedRoom>;
[Category.Leave]: Record<string, ILeftRoom>;
}
interface IPresence {
events: IMinimalEvent[];
}
interface IAccountData {
events: IMinimalEvent[];
}
interface IToDeviceEvent {
content: IContent;
sender: string;
type: string;
}
interface IToDevice {
events: IToDeviceEvent[];
}
interface IDeviceLists {
changed: string[];
left: string[];
}
export interface IGroups {
[Category.Join]: object;
[Category.Invite]: object;
[Category.Leave]: object;
}
export interface ISyncResponse {
next_batch: string;
rooms: IRooms;
presence?: IPresence;
account_data: IAccountData;
to_device?: IToDevice;
device_lists?: IDeviceLists;
device_one_time_keys_count?: Record<string, number>;
groups: IGroups; // unspecced
}
/* eslint-enable camelcase */
export enum Category {
Invite = "invite",
Leave = "leave",
Join = "join",
}
interface IRoom {
_currentState: { [eventType: string]: { [stateKey: string]: IStateEvent } };
_timeline: {
event: IRoomEvent | IStateEvent;
token: string | null;
}[];
_summary: Partial<IRoomSummary>;
_accountData: { [eventType: string]: IMinimalEvent };
_unreadNotifications: Partial<IUnreadNotificationCounts>;
_readReceipts: {
[userId: string]: {
data: IMinimalEvent;
eventId: string;
};
};
}
/** /**
* The purpose of this class is to accumulate /sync responses such that a * The purpose of this class is to accumulate /sync responses such that a
@@ -35,6 +180,22 @@ import { deepCopy } from "./utils";
* rather than asking the server to do an initial sync on startup. * rather than asking the server to do an initial sync on startup.
*/ */
export class SyncAccumulator { export class SyncAccumulator {
private accountData: Record<string, IMinimalEvent> = {}; // $event_type: Object
private inviteRooms: Record<string, IInvitedRoom> = {}; // $roomId: { ... sync 'invite' json data ... }
private joinRooms: { [roomId: string]: IRoom } = {};
// the /sync token which corresponds to the last time rooms were
// accumulated. We remember this so that any caller can obtain a
// coherent /sync response and know at what point they should be
// streaming from without losing events.
private nextBatch: string = null;
// { ('invite'|'join'|'leave'): $groupId: { ... sync 'group' data } }
private groups: Record<Category, object> = {
invite: {},
join: {},
leave: {},
};
/** /**
* @param {Object} opts * @param {Object} opts
* @param {Number=} opts.maxTimelineEntries The ideal maximum number of * @param {Number=} opts.maxTimelineEntries The ideal maximum number of
@@ -44,57 +205,18 @@ export class SyncAccumulator {
* never be more. This cannot be 0 or else it makes it impossible to scroll * never be more. This cannot be 0 or else it makes it impossible to scroll
* back in a room. Default: 50. * back in a room. Default: 50.
*/ */
constructor(opts) { constructor(private readonly opts: IOpts = {}) {
opts = opts || {}; this.opts.maxTimelineEntries = this.opts.maxTimelineEntries || 50;
opts.maxTimelineEntries = opts.maxTimelineEntries || 50;
this.opts = opts;
this.accountData = {
//$event_type: Object
};
this.inviteRooms = {
//$roomId: { ... sync 'invite' json data ... }
};
this.joinRooms = {
//$roomId: {
// _currentState: { $event_type: { $state_key: json } },
// _timeline: [
// { event: $event, token: null|token },
// { event: $event, token: null|token },
// { event: $event, token: null|token },
// ...
// ],
// _summary: {
// m.heroes: [ $user_id ],
// m.joined_member_count: $count,
// m.invited_member_count: $count
// },
// _accountData: { $event_type: json },
// _unreadNotifications: { ... unread_notifications JSON ... },
// _readReceipts: { $user_id: { data: $json, eventId: $event_id }}
//}
};
// the /sync token which corresponds to the last time rooms were
// accumulated. We remember this so that any caller can obtain a
// coherent /sync response and know at what point they should be
// streaming from without losing events.
this.nextBatch = null;
// { ('invite'|'join'|'leave'): $groupId: { ... sync 'group' data } }
this.groups = {
invite: {},
join: {},
leave: {},
};
} }
accumulate(syncResponse, fromDatabase) { public accumulate(syncResponse: ISyncResponse, fromDatabase = false): void {
this._accumulateRooms(syncResponse, fromDatabase); this.accumulateRooms(syncResponse, fromDatabase);
this._accumulateGroups(syncResponse); this.accumulateGroups(syncResponse);
this._accumulateAccountData(syncResponse); this.accumulateAccountData(syncResponse);
this.nextBatch = syncResponse.next_batch; this.nextBatch = syncResponse.next_batch;
} }
_accumulateAccountData(syncResponse) { private accumulateAccountData(syncResponse: ISyncResponse): void {
if (!syncResponse.account_data || !syncResponse.account_data.events) { if (!syncResponse.account_data || !syncResponse.account_data.events) {
return; return;
} }
@@ -109,34 +231,31 @@ export class SyncAccumulator {
* @param {Object} syncResponse the complete /sync JSON * @param {Object} syncResponse the complete /sync JSON
* @param {boolean} fromDatabase True if the sync response is one saved to the database * @param {boolean} fromDatabase True if the sync response is one saved to the database
*/ */
_accumulateRooms(syncResponse, fromDatabase) { private accumulateRooms(syncResponse: ISyncResponse, fromDatabase = false): void {
if (!syncResponse.rooms) { if (!syncResponse.rooms) {
return; return;
} }
if (syncResponse.rooms.invite) { if (syncResponse.rooms.invite) {
Object.keys(syncResponse.rooms.invite).forEach((roomId) => { Object.keys(syncResponse.rooms.invite).forEach((roomId) => {
this._accumulateRoom( this.accumulateRoom(roomId, Category.Invite, syncResponse.rooms.invite[roomId], fromDatabase);
roomId, "invite", syncResponse.rooms.invite[roomId], fromDatabase,
);
}); });
} }
if (syncResponse.rooms.join) { if (syncResponse.rooms.join) {
Object.keys(syncResponse.rooms.join).forEach((roomId) => { Object.keys(syncResponse.rooms.join).forEach((roomId) => {
this._accumulateRoom( this.accumulateRoom(roomId, Category.Join, syncResponse.rooms.join[roomId], fromDatabase);
roomId, "join", syncResponse.rooms.join[roomId], fromDatabase,
);
}); });
} }
if (syncResponse.rooms.leave) { if (syncResponse.rooms.leave) {
Object.keys(syncResponse.rooms.leave).forEach((roomId) => { Object.keys(syncResponse.rooms.leave).forEach((roomId) => {
this._accumulateRoom( this.accumulateRoom(roomId, Category.Leave, syncResponse.rooms.leave[roomId], fromDatabase);
roomId, "leave", syncResponse.rooms.leave[roomId], fromDatabase,
);
}); });
} }
} }
_accumulateRoom(roomId, category, data, fromDatabase) { 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.Leave, data: ILeftRoom, fromDatabase: boolean): 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
// +== | INVITE | | (5) 2: Leave a room // +== | INVITE | | (5) 2: Leave a room
@@ -149,10 +268,11 @@ export class SyncAccumulator {
// //
// * equivalent to "no state" // * equivalent to "no state"
switch (category) { switch (category) {
case "invite": // (5) case Category.Invite: // (5)
this._accumulateInviteState(roomId, data); this.accumulateInviteState(roomId, data as IInvitedRoom);
break; break;
case "join":
case Category.Join:
if (this.inviteRooms[roomId]) { // (1) if (this.inviteRooms[roomId]) { // (1)
// was previously invite, now join. We expect /sync to give // was previously invite, now join. We expect /sync to give
// the entire state and timeline on 'join', so delete previous // the entire state and timeline on 'join', so delete previous
@@ -160,21 +280,23 @@ export class SyncAccumulator {
delete this.inviteRooms[roomId]; delete this.inviteRooms[roomId];
} }
// (3) // (3)
this._accumulateJoinState(roomId, data, fromDatabase); this.accumulateJoinState(roomId, data as IJoinedRoom, fromDatabase);
break; break;
case "leave":
case Category.Leave:
if (this.inviteRooms[roomId]) { // (4) if (this.inviteRooms[roomId]) { // (4)
delete this.inviteRooms[roomId]; delete this.inviteRooms[roomId];
} else { // (2) } else { // (2)
delete this.joinRooms[roomId]; delete this.joinRooms[roomId];
} }
break; break;
default: default:
logger.error("Unknown cateogory: ", category); logger.error("Unknown cateogory: ", category);
} }
} }
_accumulateInviteState(roomId, data) { private accumulateInviteState(roomId: string, data: IInvitedRoom): void {
if (!data.invite_state || !data.invite_state.events) { // no new data if (!data.invite_state || !data.invite_state.events) { // no new data
return; return;
} }
@@ -204,7 +326,7 @@ export class SyncAccumulator {
} }
// Accumulate timeline and state events in a room. // Accumulate timeline and state events in a room.
_accumulateJoinState(roomId, data, fromDatabase) { 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
// this to be fast. /sync stores events in an array but we often want // this to be fast. /sync stores events in an array but we often want
// to clobber based on type/state_key. Rather than convert arrays to // to clobber based on type/state_key. Rather than convert arrays to
@@ -338,7 +460,7 @@ export class SyncAccumulator {
setState(currentData._currentState, e); setState(currentData._currentState, e);
// append the event to the timeline. The back-pagination token // append the event to the timeline. The back-pagination token
// corresponds to the first event in the timeline // corresponds to the first event in the timeline
let transformedEvent; let transformedEvent: IRoomEvent & { _localTs?: number };
if (!fromDatabase) { if (!fromDatabase) {
transformedEvent = Object.assign({}, e); transformedEvent = Object.assign({}, e);
if (transformedEvent.unsigned !== undefined) { if (transformedEvent.unsigned !== undefined) {
@@ -379,35 +501,29 @@ export class SyncAccumulator {
* Accumulate incremental /sync group data. * Accumulate incremental /sync group data.
* @param {Object} syncResponse the complete /sync JSON * @param {Object} syncResponse the complete /sync JSON
*/ */
_accumulateGroups(syncResponse) { private accumulateGroups(syncResponse: ISyncResponse): void {
if (!syncResponse.groups) { if (!syncResponse.groups) {
return; return;
} }
if (syncResponse.groups.invite) { if (syncResponse.groups.invite) {
Object.keys(syncResponse.groups.invite).forEach((groupId) => { Object.keys(syncResponse.groups.invite).forEach((groupId) => {
this._accumulateGroup( this.accumulateGroup(groupId, Category.Invite, syncResponse.groups.invite[groupId]);
groupId, "invite", syncResponse.groups.invite[groupId],
);
}); });
} }
if (syncResponse.groups.join) { if (syncResponse.groups.join) {
Object.keys(syncResponse.groups.join).forEach((groupId) => { Object.keys(syncResponse.groups.join).forEach((groupId) => {
this._accumulateGroup( this.accumulateGroup(groupId, Category.Join, syncResponse.groups.join[groupId]);
groupId, "join", syncResponse.groups.join[groupId],
);
}); });
} }
if (syncResponse.groups.leave) { if (syncResponse.groups.leave) {
Object.keys(syncResponse.groups.leave).forEach((groupId) => { Object.keys(syncResponse.groups.leave).forEach((groupId) => {
this._accumulateGroup( this.accumulateGroup(groupId, Category.Leave, syncResponse.groups.leave[groupId]);
groupId, "leave", syncResponse.groups.leave[groupId],
);
}); });
} }
} }
_accumulateGroup(groupId, category, data) { private accumulateGroup(groupId: string, category: Category, data: object): void {
for (const cat of ['invite', 'join', 'leave']) { for (const cat of [Category.Invite, Category.Leave, Category.Join]) {
delete this.groups[cat][groupId]; delete this.groups[cat][groupId];
} }
this.groups[category][groupId] = data; this.groups[category][groupId] = data;
@@ -428,7 +544,7 @@ export class SyncAccumulator {
* /sync response from the 'rooms' key onwards. The "accountData" key is * /sync response from the 'rooms' key onwards. The "accountData" key is
* a list of raw events which represent global account data. * a list of raw events which represent global account data.
*/ */
getJSON(forDatabase) { public getJSON(forDatabase = false): object {
const data = { const data = {
join: {}, join: {},
invite: {}, invite: {},
@@ -501,14 +617,14 @@ export class SyncAccumulator {
roomJson.timeline.prev_batch = msgData.token; roomJson.timeline.prev_batch = msgData.token;
} }
let transformedEvent; let transformedEvent: (IRoomEvent | IStateEvent) & { _localTs?: number };
if (!forDatabase && msgData.event._localTs) { if (!forDatabase && msgData.event["_localTs"]) {
// This means we have to copy each event so we can fix it up to // This means we have to copy each event so we can fix it up to
// set a correct 'age' parameter whilst keeping the local timestamp // set a correct 'age' parameter whilst keeping the local timestamp
// on our stored event. If this turns out to be a bottleneck, it could // on our stored event. If this turns out to be a bottleneck, it could
// be optimised either by doing this in the main process after the data // be optimised either by doing this in the main process after the data
// has been structured-cloned to go between the worker & main process, // has been structured-cloned to go between the worker & main process,
// or special-casing data from saved syncs to read the local timstamp // or special-casing data from saved syncs to read the local timestamp
// directly rather than turning it into age to then immediately be // directly rather than turning it into age to then immediately be
// transformed back again into a local timestamp. // transformed back again into a local timestamp.
transformedEvent = Object.assign({}, msgData.event); transformedEvent = Object.assign({}, msgData.event);
@@ -517,7 +633,7 @@ export class SyncAccumulator {
} }
delete transformedEvent._localTs; delete transformedEvent._localTs;
transformedEvent.unsigned = transformedEvent.unsigned || {}; transformedEvent.unsigned = transformedEvent.unsigned || {};
transformedEvent.unsigned.age = Date.now() - msgData.event._localTs; transformedEvent.unsigned.age = Date.now() - msgData.event["_localTs"];
} else { } else {
transformedEvent = msgData.event; transformedEvent = msgData.event;
} }
@@ -575,17 +691,17 @@ export class SyncAccumulator {
}; };
} }
getNextBatchToken() { public getNextBatchToken(): string {
return this.nextBatch; return this.nextBatch;
} }
} }
function setState(eventMap, event) { function setState(eventMap: Record<string, Record<string, IStateEvent>>, event: IRoomEvent | IStateEvent): void {
if (event.state_key === null || event.state_key === undefined || !event.type) { if ((event as IStateEvent).state_key === null || (event as IStateEvent).state_key === undefined || !event.type) {
return; return;
} }
if (!eventMap[event.type]) { if (!eventMap[event.type]) {
eventMap[event.type] = Object.create(null); eventMap[event.type] = Object.create(null);
} }
eventMap[event.type][event.state_key] = event; eventMap[event.type][(event as IStateEvent).state_key] = event as IStateEvent;
} }

File diff suppressed because it is too large Load Diff

1745
src/sync.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -438,7 +438,7 @@ export function isNullOrUndefined(val: any): boolean {
export interface IDeferred<T> { export interface IDeferred<T> {
resolve: (value: T) => void; resolve: (value: T) => void;
reject: (any) => void; reject: (reason?: any) => void;
promise: Promise<T>; promise: Promise<T>;
} }
@@ -456,10 +456,10 @@ export function defer<T>(): IDeferred<T> {
} }
export async function promiseMapSeries<T>( export async function promiseMapSeries<T>(
promises: Promise<T>[], promises: T[],
fn: (t: T) => void, fn: (t: T) => void,
): Promise<void> { ): Promise<void> {
for (const o of await promises) { for (const o of promises) {
await fn(await o); await fn(await o);
} }
} }