forked from mystiq/hydrogen-web
ignore rooms with empty timelines during initial sync
This commit is contained in:
parent
989a27395e
commit
59588dc8b5
2 changed files with 134 additions and 0 deletions
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
Copyright 2020 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.
|
||||||
|
@ -17,6 +18,7 @@ limitations under the License.
|
||||||
import {AbortError} from "./error.js";
|
import {AbortError} from "./error.js";
|
||||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
import {ObservableValue} from "../observable/ObservableValue.js";
|
||||||
import {createEnum} from "../utils/enum.js";
|
import {createEnum} from "../utils/enum.js";
|
||||||
|
import {readPath, Type} from "../utils/validate.js";
|
||||||
|
|
||||||
const INCREMENTAL_TIMEOUT = 30000;
|
const INCREMENTAL_TIMEOUT = 30000;
|
||||||
const SYNC_EVENT_LIMIT = 10;
|
const SYNC_EVENT_LIMIT = 10;
|
||||||
|
@ -43,6 +45,15 @@ function parseRooms(roomsSection, roomCallback) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timelineIsEmpty(roomResponse) {
|
||||||
|
try {
|
||||||
|
const events = readPath(roomResponse, ["timeline", "events"], Type.Array);
|
||||||
|
return events.length === 0;
|
||||||
|
} catch (err) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Sync {
|
export class Sync {
|
||||||
constructor({hsApi, session, storage}) {
|
constructor({hsApi, session, storage}) {
|
||||||
this._hsApi = hsApi;
|
this._hsApi = hsApi;
|
||||||
|
@ -102,6 +113,7 @@ export class Sync {
|
||||||
const totalRequestTimeout = timeout + (80 * 1000); // same as riot-web, don't get stuck on wedged long requests
|
const totalRequestTimeout = timeout + (80 * 1000); // same as riot-web, don't get stuck on wedged long requests
|
||||||
this._currentRequest = this._hsApi.sync(syncToken, syncFilterId, timeout, {timeout: totalRequestTimeout});
|
this._currentRequest = this._hsApi.sync(syncToken, syncFilterId, timeout, {timeout: totalRequestTimeout});
|
||||||
const response = await this._currentRequest.response();
|
const response = await this._currentRequest.response();
|
||||||
|
const isInitialSync = !syncToken;
|
||||||
syncToken = response.next_batch;
|
syncToken = response.next_batch;
|
||||||
const storeNames = this._storage.storeNames;
|
const storeNames = this._storage.storeNames;
|
||||||
const syncTxn = await this._storage.readWriteTxn([
|
const syncTxn = await this._storage.readWriteTxn([
|
||||||
|
@ -120,6 +132,11 @@ export class Sync {
|
||||||
// presence
|
// presence
|
||||||
if (response.rooms) {
|
if (response.rooms) {
|
||||||
const promises = parseRooms(response.rooms, async (roomId, roomResponse, membership) => {
|
const promises = parseRooms(response.rooms, async (roomId, roomResponse, membership) => {
|
||||||
|
// ignore rooms with empty timelines during initial sync,
|
||||||
|
// see https://github.com/vector-im/hydrogen-web/issues/15
|
||||||
|
if (isInitialSync && timelineIsEmpty(roomResponse)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let room = this._session.rooms.get(roomId);
|
let room = this._session.rooms.get(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
room = this._session.createRoom(roomId);
|
room = this._session.createRoom(roomId);
|
||||||
|
|
117
src/utils/validate.js
Normal file
117
src/utils/validate.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class InvalidPathError extends Error {
|
||||||
|
constructor(obj, path, field) {
|
||||||
|
super(`Could not read path ${path.join("/")}, stopped at ${field}. Base value is ${obj}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return "InvalidPathError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidTypeError extends Error {
|
||||||
|
constructor(path, fieldValue, validator) {
|
||||||
|
super(`Value ${path.join("/")} is not of type ${getTypeName(validator)} but is: ${fieldValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return "InvalidTypeError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeName(validator) {
|
||||||
|
if (validator === Type.Array) {
|
||||||
|
return "Array";
|
||||||
|
}
|
||||||
|
if (validator === Type.Integer) {
|
||||||
|
return "Integer";
|
||||||
|
}
|
||||||
|
if (validator === Type.String) {
|
||||||
|
return "String";
|
||||||
|
}
|
||||||
|
if (validator === Type.Object) {
|
||||||
|
return "Object";
|
||||||
|
}
|
||||||
|
if (typeof validator === "function") {
|
||||||
|
return "Custom";
|
||||||
|
}
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readPath(obj, path, typeOrDefaultValue) {
|
||||||
|
if (!obj) {
|
||||||
|
throw new InvalidPathError(obj, path);
|
||||||
|
}
|
||||||
|
const hasDefaultValue = typeof typeOrDefaultValue !== "function";
|
||||||
|
let currentValue = obj;
|
||||||
|
for (const field of path) {
|
||||||
|
currentValue = currentValue[field];
|
||||||
|
if (typeof currentValue === "undefined") {
|
||||||
|
if (hasDefaultValue) {
|
||||||
|
return typeOrDefaultValue;
|
||||||
|
} else {
|
||||||
|
throw new InvalidPathError(obj, path, field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasDefaultValue) {
|
||||||
|
const validator = typeOrDefaultValue;
|
||||||
|
if (!validator(currentValue)) {
|
||||||
|
throw new InvalidTypeError(path, currentValue, validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Type = Object.freeze({
|
||||||
|
"Array": Array.isArray,
|
||||||
|
"Integer": Number.isSafeInteger,
|
||||||
|
"Boolean": value => value === true || value === false,
|
||||||
|
"String": value => typeof value === "string",
|
||||||
|
"Object": value => value !== null && typeof value === "object",
|
||||||
|
});
|
||||||
|
|
||||||
|
export function tests() {
|
||||||
|
return {
|
||||||
|
"readPath value at top level": assert => {
|
||||||
|
assert.strictEqual(readPath({a: 5}, ["a"]), 5);
|
||||||
|
},
|
||||||
|
"readPath value at deep level": assert => {
|
||||||
|
assert.strictEqual(readPath({a: {b: {c: 5}}}, ["a", "b", "c"]), 5);
|
||||||
|
},
|
||||||
|
"readPath value with correct type": assert => {
|
||||||
|
assert.strictEqual(readPath({a: 5}, ["a"], Type.Integer), 5);
|
||||||
|
},
|
||||||
|
"readPath value with failing type": assert => {
|
||||||
|
assert.throws(
|
||||||
|
() => readPath({a: 5}, ["a"], Type.String),
|
||||||
|
{name: "InvalidTypeError"}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
"readPath value with failing path with intermediate field not being an object": assert => {
|
||||||
|
assert.throws(
|
||||||
|
() => readPath({a: {b: "bar"}}, ["a", "b", "c"], Type.Integer),
|
||||||
|
{name: "InvalidPathError"}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
"readPath returns default value for incomplete path": assert => {
|
||||||
|
assert.strictEqual(readPath({a: {b: "bar"}}, ["a", "b", "c"], 5), 5);
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue