You've already forked matrix-js-sdk
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:
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
1710
src/sync.js
1710
src/sync.js
File diff suppressed because it is too large
Load Diff
1745
src/sync.ts
Normal file
1745
src/sync.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user