Merge remote-tracking branch 'origin/develop' into dbkr/support_no_ssss

This commit is contained in:
David Baker 2020-06-19 16:50:29 +01:00
commit 3f936a1fe4
103 changed files with 1827 additions and 730 deletions

View file

@ -20,11 +20,10 @@ import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManage
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
export const PHASE_INTRO = 0;
export const PHASE_RECOVERY_KEY = 1;
export const PHASE_BUSY = 2;
export const PHASE_DONE = 3; //final done stage, but still showing UX
export const PHASE_CONFIRM_SKIP = 4;
export const PHASE_FINISHED = 5; //UX can be closed
export const PHASE_BUSY = 1;
export const PHASE_DONE = 2; //final done stage, but still showing UX
export const PHASE_CONFIRM_SKIP = 3;
export const PHASE_FINISHED = 4; //UX can be closed
export class SetupEncryptionStore extends EventEmitter {
static sharedInstance() {
@ -68,7 +67,7 @@ export class SetupEncryptionStore extends EventEmitter {
async fetchKeyInfo() {
const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false);
if (!keys || Object.keys(keys).length === 0) {
if (Object.keys(keys).length === 0) {
this.keyId = null;
this.keyInfo = null;
} else {
@ -81,34 +80,7 @@ export class SetupEncryptionStore extends EventEmitter {
this.emit("update");
}
async startKeyReset() {
try {
await accessSecretStorage(() => {}, {forceReset: true});
// If the keys are reset, the trust status event will fire and we'll change state
} catch (e) {
// dialog was cancelled - stay on the current screen
}
}
async useRecoveryKey() {
this.phase = PHASE_RECOVERY_KEY;
this.emit("update");
}
cancelUseRecoveryKey() {
this.phase = PHASE_INTRO;
this.emit("update");
}
async setupWithRecoveryKey(recoveryKey) {
this.startTrustCheck({[this.keyId]: recoveryKey});
}
async usePassPhrase() {
this.startTrustCheck();
}
async startTrustCheck(withKeys) {
this.phase = PHASE_BUSY;
this.emit("update");
const cli = MatrixClientPeg.get();
@ -135,9 +107,6 @@ export class SetupEncryptionStore extends EventEmitter {
// to advance before this.
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
}
}, {
withKeys,
passphraseOnly: true,
}).catch(reject);
} catch (e) {
console.error(e);

View file

@ -21,11 +21,13 @@ const TILE_HEIGHT_PX = 44;
interface ISerializedListLayout {
numTiles: number;
showPreviews: boolean;
collapsed: boolean;
}
export class ListLayout {
private _n = 0;
private _previews = false;
private _collapsed = false;
constructor(public readonly tagId: TagID) {
const serialized = localStorage.getItem(this.key);
@ -34,9 +36,19 @@ export class ListLayout {
const parsed = <ISerializedListLayout>JSON.parse(serialized);
this._n = parsed.numTiles;
this._previews = parsed.showPreviews;
this._collapsed = parsed.collapsed;
}
}
public get isCollapsed(): boolean {
return this._collapsed;
}
public set isCollapsed(v: boolean) {
this._collapsed = v;
this.save();
}
public get showPreviews(): boolean {
return this._previews;
}
@ -80,8 +92,12 @@ export class ListLayout {
return this.tilesToPixels(Math.min(maxTiles, n)) + padding;
}
public tilesToPixelsWithPadding(n: number, padding: number): number {
return this.tilesToPixels(n) + padding;
public tilesWithPadding(n: number, paddingPx: number): number {
return this.pixelsToTiles(this.tilesToPixelsWithPadding(n, paddingPx));
}
public tilesToPixelsWithPadding(n: number, paddingPx: number): number {
return this.tilesToPixels(n) + paddingPx;
}
public tilesToPixels(n: number): number {
@ -100,6 +116,7 @@ export class ListLayout {
return {
numTiles: this.visibleTiles,
showPreviews: this.showPreviews,
collapsed: this.isCollapsed,
};
}
}

View file

@ -181,6 +181,12 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
const room = this.matrixClient.getRoom(roomId);
const tryUpdate = async (updatedRoom: Room) => {
console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()} in ${updatedRoom.roomId}`);
if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') {
console.log(`[RoomListDebug] Got tombstone event - regenerating room list`);
// TODO: We could probably be smarter about this
await this.regenerateAllLists();
return; // don't pass the update down - we will have already handled it in the regen
}
await this.handleRoomUpdate(updatedRoom, RoomUpdateCause.Timeline);
};
if (!room) {
@ -334,7 +340,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
}
await this.algorithm.populateTags(sorts, orders);
await this.algorithm.setKnownRooms(this.matrixClient.getRooms());
await this.algorithm.setKnownRooms(this.matrixClient.getVisibleRooms());
this.initialListsGenerated = true;

View file

@ -127,7 +127,7 @@ export class Algorithm extends EventEmitter {
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
this.algorithms[tagId] = algorithm;
await algorithm.setRooms(this._cachedRooms[tagId])
await algorithm.setRooms(this._cachedRooms[tagId]);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@ -230,7 +230,10 @@ export class Algorithm extends EventEmitter {
// Cheaply clone the rooms so we can more easily do operations on the list.
// We optimize our lookups by trying to reduce sample size as much as possible
// to the rooms we know will be deduped by the Set.
const rooms = this.cachedRooms[tagId];
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
if (this._stickyRoom && this._stickyRoom.tag === tagId && this._stickyRoom.room) {
rooms.push(this._stickyRoom.room);
}
let remainingRooms = rooms.map(r => r);
let allowedRoomsInThisTag = [];
let lastFilterPriority = orderedFilters[0].relativePriority;
@ -505,16 +508,14 @@ export class Algorithm extends EventEmitter {
return true;
}
if (cause === RoomUpdateCause.NewRoom) {
// TODO: Be smarter and insert rather than regen the planet.
await this.setKnownRooms([room, ...this.rooms]);
return true;
}
if (cause === RoomUpdateCause.RoomRemoved) {
// TODO: Be smarter and splice rather than regen the planet.
await this.setKnownRooms(this.rooms.filter(r => r !== room));
return true;
// If the update is for a room change which might be the sticky room, prevent it. We
// need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though
// as the sticky room relies on this.
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
if (this.stickyRoom === room) {
console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`);
return false;
}
}
let tags = this.roomIdsToTags[room.roomId];
@ -538,5 +539,5 @@ export class Algorithm extends EventEmitter {
}
return true;
};
}
}

View file

@ -87,7 +87,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
super(tagId, initialSortingAlgorithm);
console.log("Constructed an ImportanceAlgorithm");
console.log(`[RoomListDebug] Constructed an ImportanceAlgorithm for ${tagId}`);
}
// noinspection JSMethodCanBeStatic
@ -151,8 +151,36 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
}
}
private async handleSplice(room: Room, cause: RoomUpdateCause): Promise<boolean> {
if (cause === RoomUpdateCause.NewRoom) {
const category = this.getRoomCategory(room);
this.alterCategoryPositionBy(category, 1, this.indices);
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
} else if (cause === RoomUpdateCause.RoomRemoved) {
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) return false; // no change
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
this.alterCategoryPositionBy(oldCategory, -1, this.indices);
this.cachedOrderedRooms.splice(roomIdx, 1); // remove the room
} else {
throw new Error(`Unhandled splice: ${cause}`);
}
}
private getRoomIndex(room: Room): number {
let roomIdx = this.cachedOrderedRooms.indexOf(room);
if (roomIdx === -1) { // can only happen if the js-sdk's store goes sideways.
console.warn(`Degrading performance to find missing room in "${this.tagId}": ${room.roomId}`);
roomIdx = this.cachedOrderedRooms.findIndex(r => r.roomId === room.roomId);
}
return roomIdx;
}
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
// TODO: Handle NewRoom and RoomRemoved
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
return this.handleSplice(room, cause);
}
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
throw new Error(`Unsupported update cause: ${cause}`);
}
@ -162,11 +190,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return; // Nothing to do here.
}
let roomIdx = this.cachedOrderedRooms.indexOf(room);
if (roomIdx === -1) { // can only happen if the js-sdk's store goes sideways.
console.warn(`Degrading performance to find missing room in "${this.tagId}": ${room.roomId}`);
roomIdx = this.cachedOrderedRooms.findIndex(r => r.roomId === room.roomId);
}
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
}
@ -188,12 +212,18 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
// room from the array.
}
// The room received an update, so take out the slice and sort it. This should be relatively
// quick because the room is inserted at the top of the category, and most popular sorting
// algorithms will deal with trying to keep the active room at the top/start of the category.
// For the few algorithms that will have to move the thing quite far (alphabetic with a Z room
// for example), the list should already be sorted well enough that it can rip through the
// array and slot the changed room in quickly.
// Sort the category now that we've dumped the room in
await this.sortCategory(category);
return true; // change made
}
private async sortCategory(category: Category) {
// This should be relatively quick because the room is usually inserted at the top of the
// category, and most popular sorting algorithms will deal with trying to keep the active
// room at the top/start of the category. For the few algorithms that will have to move the
// thing quite far (alphabetic with a Z room for example), the list should already be sorted
// well enough that it can rip through the array and slot the changed room in quickly.
const nextCategoryStartIdx = category === CATEGORY_ORDER[CATEGORY_ORDER.length - 1]
? Number.MAX_SAFE_INTEGER
: this.indices[CATEGORY_ORDER[CATEGORY_ORDER.indexOf(category) + 1]];
@ -202,8 +232,6 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
const unsortedSlice = this.cachedOrderedRooms.splice(startIdx, numSort);
const sorted = await sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
this.cachedOrderedRooms.splice(startIdx, 0, ...sorted);
return true; // change made
}
// noinspection JSMethodCanBeStatic
@ -230,14 +258,29 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
// We also need to update subsequent categories as they'll all shift by nRooms, so we
// loop over the order to achieve that.
for (let i = CATEGORY_ORDER.indexOf(fromCategory) + 1; i < CATEGORY_ORDER.length; i++) {
const nextCategory = CATEGORY_ORDER[i];
indices[nextCategory] -= nRooms;
}
this.alterCategoryPositionBy(fromCategory, -nRooms, indices);
this.alterCategoryPositionBy(toCategory, +nRooms, indices);
}
for (let i = CATEGORY_ORDER.indexOf(toCategory) + 1; i < CATEGORY_ORDER.length; i++) {
const nextCategory = CATEGORY_ORDER[i];
indices[nextCategory] += nRooms;
private alterCategoryPositionBy(category: Category, n: number, indices: ICategoryIndex) {
// Note: when we alter a category's index, we actually have to modify the ones following
// the target and not the target itself.
// XXX: If this ever actually gets more than one room passed to it, it'll need more index
// handling. For instance, if 45 rooms are removed from the middle of a 50 room list, the
// index for the categories will be way off.
const nextOrderIndex = CATEGORY_ORDER.indexOf(category) + 1;
if (n > 0) {
for (let i = nextOrderIndex; i < CATEGORY_ORDER.length; i++) {
const nextCategory = CATEGORY_ORDER[i];
indices[nextCategory] += Math.abs(n);
}
} else if (n < 0) {
for (let i = nextOrderIndex; i < CATEGORY_ORDER.length; i++) {
const nextCategory = CATEGORY_ORDER[i];
indices[nextCategory] -= Math.abs(n);
}
}
// Do a quick check to see if we've completely broken the index

View file

@ -28,7 +28,7 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
super(tagId, initialSortingAlgorithm);
console.log("Constructed a NaturalAlgorithm");
console.log(`[RoomListDebug] Constructed a NaturalAlgorithm for ${tagId}`);
}
public async setRooms(rooms: Room[]): Promise<any> {
@ -36,11 +36,19 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
}
public async handleRoomUpdate(room, cause): Promise<boolean> {
// TODO: Handle NewRoom and RoomRemoved
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
if (!isSplice && !isInPlace) {
throw new Error(`Unsupported update cause: ${cause}`);
}
if (cause === RoomUpdateCause.NewRoom) {
this.cachedOrderedRooms.push(room);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const idx = this.cachedOrderedRooms.indexOf(room);
if (idx >= 0) this.cachedOrderedRooms.splice(idx, 1);
}
// TODO: Optimize this to avoid useless operations
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm);

View file

@ -67,6 +67,5 @@ export abstract class OrderingAlgorithm {
* @param cause The cause of the update.
* @returns True if the update requires the Algorithm to update the presentation layers.
*/
// XXX: TODO: We assume this will only ever be a position update and NOT a NewRoom or RemoveRoom change!!
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>;
}

View file

@ -75,7 +75,7 @@ export class RecentAlgorithm implements IAlgorithm {
};
return rooms.sort((a, b) => {
return getLastTs(a) - getLastTs(b);
return getLastTs(b) - getLastTs(a);
});
}
}

View file

@ -17,6 +17,7 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "./IFilterCondition";
import { EventEmitter } from "events";
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
/**
* A filter condition for the room list which reveals rooms of a particular
@ -45,7 +46,24 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
}
public isVisible(room: Room): boolean {
// TODO: Improve this filter to include aliases and such
return room.name.toLowerCase().indexOf(this.search.toLowerCase()) >= 0;
const lcFilter = this.search.toLowerCase();
if (this.search[0] === '#') {
// Try and find rooms by alias
if (room.getCanonicalAlias() && room.getCanonicalAlias().toLowerCase().startsWith(lcFilter)) {
return true;
}
if (room.getAltAliases().some(a => a.toLowerCase().startsWith(lcFilter))) {
return true;
}
}
if (!room.name) return false; // should realisitically not happen: the js-sdk always calculates a name
// Note: we have to match the filter with the removeHiddenChars() room name because the
// function strips spaces and other characters (M becomes RN for example, in lowercase).
// We also doubly convert to lowercase to work around oddities of the library.
const noSecretsFilter = removeHiddenChars(lcFilter).toLowerCase();
const noSecretsName = removeHiddenChars(room.name.toLowerCase()).toLowerCase();
return noSecretsName.includes(noSecretsFilter);
}
}