Merge pull request #475 from vector-im/snowpack-ts-storage-4

Snowpack + Typescript conversion (Part 4)
This commit is contained in:
Bruno Windels 2021-09-06 13:05:08 +02:00 committed by GitHub
commit ed082c9869
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 229 additions and 197 deletions

View File

@ -14,28 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Transaction} from "./Transaction.js";
import {Transaction} from "./Transaction";
import { STORE_NAMES, StoreNames, StorageError } from "../common";
import { reqAsPromise } from "./utils";
const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey";
export class Storage {
constructor(idbDatabase, IDBKeyRange, hasWebkitEarlyCloseTxnBug) {
private _db: IDBDatabase;
private _hasWebkitEarlyCloseTxnBug: boolean;
private _IDBKeyRange: typeof IDBKeyRange
storeNames: typeof StoreNames;
constructor(idbDatabase: IDBDatabase, _IDBKeyRange: typeof IDBKeyRange, hasWebkitEarlyCloseTxnBug: boolean) {
this._db = idbDatabase;
this._IDBKeyRange = IDBKeyRange;
this._IDBKeyRange = _IDBKeyRange;
this._hasWebkitEarlyCloseTxnBug = hasWebkitEarlyCloseTxnBug;
this.storeNames = StoreNames;
}
_validateStoreNames(storeNames) {
_validateStoreNames(storeNames: StoreNames[]): void {
const idx = storeNames.findIndex(name => !STORE_NAMES.includes(name));
if (idx !== -1) {
throw new StorageError(`Tried top, a transaction unknown store ${storeNames[idx]}`);
}
}
async readTxn(storeNames) {
async readTxn(storeNames: StoreNames[]): Promise<Transaction> {
this._validateStoreNames(storeNames);
try {
const txn = this._db.transaction(storeNames, "readonly");
@ -50,7 +55,7 @@ export class Storage {
}
}
async readWriteTxn(storeNames) {
async readWriteTxn(storeNames: StoreNames[]): Promise<Transaction> {
this._validateStoreNames(storeNames);
try {
const txn = this._db.transaction(storeNames, "readwrite");
@ -65,7 +70,7 @@ export class Storage {
}
}
close() {
close(): void {
this._db.close();
}
}

View File

@ -14,19 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Storage} from "./Storage.js";
import {Storage} from "./Storage";
import { openDatabase, reqAsPromise } from "./utils";
import { exportSession, importSession } from "./export.js";
import { schema } from "./schema.js";
import { detectWebkitEarlyCloseTxnBug } from "./quirks.js";
import { exportSession, importSession, Export } from "./export";
import { schema } from "./schema";
import { detectWebkitEarlyCloseTxnBug } from "./quirks";
import { BaseLogger } from "../../../logging/BaseLogger.js";
const sessionName = sessionId => `hydrogen_session_${sessionId}`;
const openDatabaseWithSessionId = function(sessionId, idbFactory, log) {
const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`;
const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, log?: BaseLogger) {
const create = (db, txn, oldVersion, version) => createStores(db, txn, oldVersion, version, log);
return openDatabase(sessionName(sessionId), create, schema.length, idbFactory);
}
async function requestPersistedStorage() {
interface ServiceWorkerHandler {
preventConcurrentSessionAccess: (sessionId: string) => Promise<void>;
}
async function requestPersistedStorage(): Promise<boolean> {
// don't assume browser so we can run in node with fake-idb
const glob = this;
if (glob?.navigator?.storage?.persist) {
@ -44,13 +49,17 @@ async function requestPersistedStorage() {
}
export class StorageFactory {
constructor(serviceWorkerHandler, idbFactory = window.indexedDB, IDBKeyRange = window.IDBKeyRange) {
private _serviceWorkerHandler: ServiceWorkerHandler;
private _idbFactory: IDBFactory;
private _IDBKeyRange: typeof IDBKeyRange
constructor(serviceWorkerHandler: ServiceWorkerHandler, idbFactory: IDBFactory = window.indexedDB, _IDBKeyRange = window.IDBKeyRange) {
this._serviceWorkerHandler = serviceWorkerHandler;
this._idbFactory = idbFactory;
this._IDBKeyRange = IDBKeyRange;
this._IDBKeyRange = _IDBKeyRange;
}
async create(sessionId, log) {
async create(sessionId: string, log?: BaseLogger): Promise<Storage> {
await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId);
requestPersistedStorage().then(persisted => {
// Firefox lies here though, and returns true even if the user denied the request
@ -64,24 +73,24 @@ export class StorageFactory {
return new Storage(db, this._IDBKeyRange, hasWebkitEarlyCloseTxnBug);
}
delete(sessionId) {
delete(sessionId: string): Promise<IDBDatabase> {
const databaseName = sessionName(sessionId);
const req = this._idbFactory.deleteDatabase(databaseName);
return reqAsPromise(req);
}
async export(sessionId) {
async export(sessionId: string): Promise<Export> {
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
return await exportSession(db);
}
async import(sessionId, data) {
async import(sessionId: string, data: Export): Promise<void> {
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
return await importSession(db, data);
}
}
async function createStores(db, txn, oldVersion, version, log) {
async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, log?: BaseLogger): Promise<void> {
const startIdx = oldVersion || 0;
return log.wrap({l: "storage migration", oldVersion, version}, async log => {
for(let i = startIdx; i < version; ++i) {

View File

@ -1,142 +0,0 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
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.
*/
import {txnAsPromise} from "./utils";
import {StorageError} from "../common";
import {Store} from "./Store";
import {SessionStore} from "./stores/SessionStore";
import {RoomSummaryStore} from "./stores/RoomSummaryStore";
import {InviteStore} from "./stores/InviteStore";
import {TimelineEventStore} from "./stores/TimelineEventStore";
import {TimelineRelationStore} from "./stores/TimelineRelationStore";
import {RoomStateStore} from "./stores/RoomStateStore";
import {RoomMemberStore} from "./stores/RoomMemberStore";
import {TimelineFragmentStore} from "./stores/TimelineFragmentStore";
import {PendingEventStore} from "./stores/PendingEventStore";
import {UserIdentityStore} from "./stores/UserIdentityStore";
import {DeviceIdentityStore} from "./stores/DeviceIdentityStore";
import {OlmSessionStore} from "./stores/OlmSessionStore";
import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore";
import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore";
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore";
import {OperationStore} from "./stores/OperationStore";
import {AccountDataStore} from "./stores/AccountDataStore";
export class Transaction {
constructor(txn, allowedStoreNames, IDBKeyRange) {
this._txn = txn;
this._allowedStoreNames = allowedStoreNames;
this._stores = {};
this.IDBKeyRange = IDBKeyRange;
}
_idbStore(name) {
if (!this._allowedStoreNames.includes(name)) {
// more specific error? this is a bug, so maybe not ...
throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
}
return new Store(this._txn.objectStore(name), this);
}
_store(name, mapStore) {
if (!this._stores[name]) {
const idbStore = this._idbStore(name);
this._stores[name] = mapStore(idbStore);
}
return this._stores[name];
}
get session() {
return this._store("session", idbStore => new SessionStore(idbStore));
}
get roomSummary() {
return this._store("roomSummary", idbStore => new RoomSummaryStore(idbStore));
}
get archivedRoomSummary() {
return this._store("archivedRoomSummary", idbStore => new RoomSummaryStore(idbStore));
}
get invites() {
return this._store("invites", idbStore => new InviteStore(idbStore));
}
get timelineFragments() {
return this._store("timelineFragments", idbStore => new TimelineFragmentStore(idbStore));
}
get timelineEvents() {
return this._store("timelineEvents", idbStore => new TimelineEventStore(idbStore));
}
get timelineRelations() {
return this._store("timelineRelations", idbStore => new TimelineRelationStore(idbStore));
}
get roomState() {
return this._store("roomState", idbStore => new RoomStateStore(idbStore));
}
get roomMembers() {
return this._store("roomMembers", idbStore => new RoomMemberStore(idbStore));
}
get pendingEvents() {
return this._store("pendingEvents", idbStore => new PendingEventStore(idbStore));
}
get userIdentities() {
return this._store("userIdentities", idbStore => new UserIdentityStore(idbStore));
}
get deviceIdentities() {
return this._store("deviceIdentities", idbStore => new DeviceIdentityStore(idbStore));
}
get olmSessions() {
return this._store("olmSessions", idbStore => new OlmSessionStore(idbStore));
}
get inboundGroupSessions() {
return this._store("inboundGroupSessions", idbStore => new InboundGroupSessionStore(idbStore));
}
get outboundGroupSessions() {
return this._store("outboundGroupSessions", idbStore => new OutboundGroupSessionStore(idbStore));
}
get groupSessionDecryptions() {
return this._store("groupSessionDecryptions", idbStore => new GroupSessionDecryptionStore(idbStore));
}
get operations() {
return this._store("operations", idbStore => new OperationStore(idbStore));
}
get accountData() {
return this._store("accountData", idbStore => new AccountDataStore(idbStore));
}
complete() {
return txnAsPromise(this._txn);
}
abort() {
// TODO: should we wrap the exception in a StorageError?
this._txn.abort();
}
}

View File

@ -0,0 +1,148 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
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.
*/
import {StoreNames} from "../common";
import {txnAsPromise} from "./utils";
import {StorageError} from "../common";
import {Store} from "./Store";
import {SessionStore} from "./stores/SessionStore";
import {RoomSummaryStore} from "./stores/RoomSummaryStore";
import {InviteStore} from "./stores/InviteStore";
import {TimelineEventStore} from "./stores/TimelineEventStore";
import {TimelineRelationStore} from "./stores/TimelineRelationStore";
import {RoomStateStore} from "./stores/RoomStateStore";
import {RoomMemberStore} from "./stores/RoomMemberStore";
import {TimelineFragmentStore} from "./stores/TimelineFragmentStore";
import {PendingEventStore} from "./stores/PendingEventStore";
import {UserIdentityStore} from "./stores/UserIdentityStore";
import {DeviceIdentityStore} from "./stores/DeviceIdentityStore";
import {OlmSessionStore} from "./stores/OlmSessionStore";
import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore";
import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore";
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore";
import {OperationStore} from "./stores/OperationStore";
import {AccountDataStore} from "./stores/AccountDataStore";
export class Transaction {
private _txn: IDBTransaction;
private _allowedStoreNames: StoreNames[];
private _stores: { [storeName in StoreNames]?: any };
constructor(txn: IDBTransaction, allowedStoreNames: StoreNames[], IDBKeyRange) {
this._txn = txn;
this._allowedStoreNames = allowedStoreNames;
this._stores = {};
// @ts-ignore
this.IDBKeyRange = IDBKeyRange;
}
_idbStore(name: StoreNames): Store<any> {
if (!this._allowedStoreNames.includes(name)) {
// more specific error? this is a bug, so maybe not ...
throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
}
return new Store(this._txn.objectStore(name), this);
}
_store<T>(name: StoreNames, mapStore: (idbStore: Store<any>) => T): T {
if (!this._stores[name]) {
const idbStore = this._idbStore(name);
this._stores[name] = mapStore(idbStore);
}
return this._stores[name];
}
get session(): SessionStore {
return this._store(StoreNames.session, idbStore => new SessionStore(idbStore));
}
get roomSummary(): RoomSummaryStore {
return this._store(StoreNames.roomSummary, idbStore => new RoomSummaryStore(idbStore));
}
get archivedRoomSummary(): RoomSummaryStore {
return this._store(StoreNames.archivedRoomSummary, idbStore => new RoomSummaryStore(idbStore));
}
get invites(): InviteStore {
return this._store(StoreNames.invites, idbStore => new InviteStore(idbStore));
}
get timelineFragments(): TimelineFragmentStore {
return this._store(StoreNames.timelineFragments, idbStore => new TimelineFragmentStore(idbStore));
}
get timelineEvents(): TimelineEventStore {
return this._store(StoreNames.timelineEvents, idbStore => new TimelineEventStore(idbStore));
}
get timelineRelations(): TimelineRelationStore {
return this._store(StoreNames.timelineRelations, idbStore => new TimelineRelationStore(idbStore));
}
get roomState(): RoomStateStore {
return this._store(StoreNames.roomState, idbStore => new RoomStateStore(idbStore));
}
get roomMembers(): RoomMemberStore {
return this._store(StoreNames.roomMembers, idbStore => new RoomMemberStore(idbStore));
}
get pendingEvents(): PendingEventStore {
return this._store(StoreNames.pendingEvents, idbStore => new PendingEventStore(idbStore));
}
get userIdentities(): UserIdentityStore {
return this._store(StoreNames.userIdentities, idbStore => new UserIdentityStore(idbStore));
}
get deviceIdentities(): DeviceIdentityStore {
return this._store(StoreNames.deviceIdentities, idbStore => new DeviceIdentityStore(idbStore));
}
get olmSessions(): OlmSessionStore {
return this._store(StoreNames.olmSessions, idbStore => new OlmSessionStore(idbStore));
}
get inboundGroupSessions(): InboundGroupSessionStore {
return this._store(StoreNames.inboundGroupSessions, idbStore => new InboundGroupSessionStore(idbStore));
}
get outboundGroupSessions(): OutboundGroupSessionStore {
return this._store(StoreNames.outboundGroupSessions, idbStore => new OutboundGroupSessionStore(idbStore));
}
get groupSessionDecryptions(): GroupSessionDecryptionStore {
return this._store(StoreNames.groupSessionDecryptions, idbStore => new GroupSessionDecryptionStore(idbStore));
}
get operations(): OperationStore {
return this._store(StoreNames.operations, idbStore => new OperationStore(idbStore));
}
get accountData(): AccountDataStore {
return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore));
}
complete(): Promise<void> {
return txnAsPromise(this._txn);
}
abort(): void {
// TODO: should we wrap the exception in a StorageError?
this._txn.abort();
}
}

View File

@ -14,25 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { iterateCursor, txnAsPromise } from "./utils";
import { STORE_NAMES } from "../common";
import { iterateCursor, NOT_DONE, txnAsPromise } from "./utils";
import { STORE_NAMES, StoreNames } from "../common";
export async function exportSession(db) {
const NOT_DONE = {done: false};
export type Export = { [storeName in StoreNames] : any[] }
export async function exportSession(db: IDBDatabase): Promise<Export> {
const txn = db.transaction(STORE_NAMES, "readonly");
const data = {};
await Promise.all(STORE_NAMES.map(async name => {
const results = data[name] = []; // initialize in deterministic order
const results: any[] = data[name] = []; // initialize in deterministic order
const store = txn.objectStore(name);
await iterateCursor(store.openCursor(), (value) => {
await iterateCursor<any>(store.openCursor(), (value) => {
results.push(value);
return NOT_DONE;
});
}));
return data;
return data as Export;
}
export async function importSession(db, data) {
export async function importSession(db: IDBDatabase, data: Export): Promise<void> {
const txn = db.transaction(STORE_NAMES, "readwrite");
for (const name of STORE_NAMES) {
const store = txn.objectStore(name);

View File

@ -18,7 +18,7 @@ limitations under the License.
import {openDatabase, txnAsPromise, reqAsPromise} from "./utils";
// filed as https://bugs.webkit.org/show_bug.cgi?id=222746
export async function detectWebkitEarlyCloseTxnBug(idbFactory) {
export async function detectWebkitEarlyCloseTxnBug(idbFactory: IDBFactory): Promise<boolean> {
const dbName = "hydrogen_webkit_test_inactive_txn_bug";
try {
const db = await openDatabase(dbName, db => {

View File

@ -1,7 +1,9 @@
import {iterateCursor, reqAsPromise} from "./utils";
import {iterateCursor, NOT_DONE, reqAsPromise} from "./utils";
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/RoomMember.js";
import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js";
import {RoomMemberStore} from "./stores/RoomMemberStore";
import {SummaryData} from "../../room/RoomSummary";
import {RoomMemberStore, MemberData} from "./stores/RoomMemberStore";
import {RoomStateEntry} from "./stores/RoomStateStore";
import {SessionStore} from "./stores/SessionStore";
import {encodeScopeTypeKey} from "./stores/OperationStore";
import {MAX_UNICODE} from "./stores/common";
@ -23,10 +25,12 @@ export const schema = [
];
// TODO: how to deal with git merge conflicts of this array?
// TypeScript note: for now, do not bother introducing interfaces / alias
// for old schemas. Just take them as `any`.
// how do we deal with schema updates vs existing data migration in a way that
//v1
function createInitialStores(db) {
function createInitialStores(db: IDBDatabase): void {
db.createObjectStore("session", {keyPath: "key"});
// any way to make keys unique here? (just use put?)
db.createObjectStore("roomSummary", {keyPath: "roomId"});
@ -43,11 +47,12 @@ function createInitialStores(db) {
db.createObjectStore("pendingEvents", {keyPath: "key"});
}
//v2
async function createMemberStore(db, txn) {
const roomMembers = new RoomMemberStore(db.createObjectStore("roomMembers", {keyPath: "key"}));
async function createMemberStore(db: IDBDatabase, txn: IDBTransaction): Promise<void> {
// Cast ok here because only "set" is used
const roomMembers = new RoomMemberStore(db.createObjectStore("roomMembers", {keyPath: "key"}) as any);
// migrate existing member state events over
const roomState = txn.objectStore("roomState");
await iterateCursor(roomState.openCursor(), entry => {
await iterateCursor<RoomStateEntry>(roomState.openCursor(), entry => {
if (entry.event.type === MEMBER_EVENT_TYPE) {
roomState.delete(entry.key);
const member = RoomMember.fromMemberEvent(entry.roomId, entry.event);
@ -55,10 +60,11 @@ async function createMemberStore(db, txn) {
roomMembers.set(member.serialize());
}
}
return NOT_DONE;
});
}
//v3
async function migrateSession(db, txn) {
async function migrateSession(db: IDBDatabase, txn: IDBTransaction): Promise<void> {
const session = txn.objectStore("session");
try {
const PRE_MIGRATION_KEY = 1;
@ -66,7 +72,8 @@ async function migrateSession(db, txn) {
if (entry) {
session.delete(PRE_MIGRATION_KEY);
const {syncToken, syncFilterId, serverVersions} = entry.value;
const store = new SessionStore(session);
// Cast ok here because only "set" is used and we don't look into return
const store = new SessionStore(session as any);
store.set("sync", {token: syncToken, filterId: syncFilterId});
store.set("serverVersions", serverVersions);
}
@ -76,7 +83,7 @@ async function migrateSession(db, txn) {
}
}
//v4
function createE2EEStores(db) {
function createE2EEStores(db: IDBDatabase): void {
db.createObjectStore("userIdentities", {keyPath: "userId"});
const deviceIdentities = db.createObjectStore("deviceIdentities", {keyPath: "key"});
deviceIdentities.createIndex("byCurve25519Key", "curve25519Key", {unique: true});
@ -89,13 +96,14 @@ function createE2EEStores(db) {
}
// v5
async function migrateEncryptionFlag(db, txn) {
async function migrateEncryptionFlag(db: IDBDatabase, txn: IDBTransaction): Promise<void> {
// migrate room summary isEncrypted -> encryption prop
const roomSummary = txn.objectStore("roomSummary");
const roomState = txn.objectStore("roomState");
const summaries = [];
await iterateCursor(roomSummary.openCursor(), summary => {
const summaries: any[] = [];
await iterateCursor<any>(roomSummary.openCursor(), summary => {
summaries.push(summary);
return NOT_DONE;
});
for (const summary of summaries) {
const encryptionEntry = await reqAsPromise(roomState.get(`${summary.roomId}|m.room.encryption|`));
@ -108,31 +116,32 @@ async function migrateEncryptionFlag(db, txn) {
}
// v6
function createAccountDataStore(db) {
function createAccountDataStore(db: IDBDatabase): void {
db.createObjectStore("accountData", {keyPath: "type"});
}
// v7
function createInviteStore(db) {
function createInviteStore(db: IDBDatabase): void {
db.createObjectStore("invites", {keyPath: "roomId"});
}
// v8
function createArchivedRoomSummaryStore(db) {
function createArchivedRoomSummaryStore(db: IDBDatabase): void {
db.createObjectStore("archivedRoomSummary", {keyPath: "summary.roomId"});
}
// v9
async function migrateOperationScopeIndex(db, txn) {
async function migrateOperationScopeIndex(db: IDBDatabase, txn: IDBTransaction): Promise<void> {
try {
const operations = txn.objectStore("operations");
operations.deleteIndex("byTypeAndScope");
await iterateCursor(operations.openCursor(), (op, key, cur) => {
await iterateCursor<any>(operations.openCursor(), (op, key, cur) => {
const {typeScopeKey} = op;
delete op.typeScopeKey;
const [type, scope] = typeScopeKey.split("|");
op.scopeTypeKey = encodeScopeTypeKey(scope, type);
cur.update(op);
return NOT_DONE;
});
operations.createIndex("byScopeAndType", "scopeTypeKey", {unique: false});
} catch (err) {
@ -142,31 +151,33 @@ async function migrateOperationScopeIndex(db, txn) {
}
//v10
function createTimelineRelationsStore(db) {
function createTimelineRelationsStore(db: IDBDatabase) : void {
db.createObjectStore("timelineRelations", {keyPath: "key"});
}
//v11 doesn't change the schema, but ensures all userIdentities have all the roomIds they should (see #470)
async function fixMissingRoomsInUserIdentities(db, txn, log) {
const roomSummaryStore = txn.objectStore("roomSummary");
const trackedRoomIds = [];
await iterateCursor(roomSummaryStore.openCursor(), roomSummary => {
const trackedRoomIds: string[] = [];
await iterateCursor<SummaryData>(roomSummaryStore.openCursor(), roomSummary => {
if (roomSummary.isTrackingMembers) {
trackedRoomIds.push(roomSummary.roomId);
}
return NOT_DONE;
});
const outboundGroupSessionsStore = txn.objectStore("outboundGroupSessions");
const userIdentitiesStore = txn.objectStore("userIdentities");
const userIdentitiesStore: IDBObjectStore = txn.objectStore("userIdentities");
const roomMemberStore = txn.objectStore("roomMembers");
for (const roomId of trackedRoomIds) {
let foundMissing = false;
const joinedUserIds = [];
const joinedUserIds: string[] = [];
const memberRange = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
await log.wrap({l: "room", id: roomId}, async log => {
await iterateCursor(roomMemberStore.openCursor(memberRange), member => {
await iterateCursor<MemberData>(roomMemberStore.openCursor(memberRange), member => {
if (member.membership === "join") {
joinedUserIds.push(member.userId);
}
return NOT_DONE;
});
log.set("joinedUserIds", joinedUserIds.length);
for (const userId of joinedUserIds) {

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js";
import {StorageFactory} from "../matrix/storage/idb/StorageFactory.js";
import {StorageFactory} from "../matrix/storage/idb/StorageFactory";
import {NullLogItem} from "../logging/NullLogger.js";
export function createMockStorage() {

View File

@ -16,7 +16,7 @@ limitations under the License.
import {createFetchRequest} from "./dom/request/fetch.js";
import {xhrRequest} from "./dom/request/xhr.js";
import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js";
import {StorageFactory} from "../../matrix/storage/idb/StorageFactory";
import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js";
import {SettingsStorage} from "./dom/SettingsStorage.js";
import {Encoding} from "./utils/Encoding.js";