forked from mystiq/hydrogen-web
Merge pull request #475 from vector-im/snowpack-ts-storage-4
Snowpack + Typescript conversion (Part 4)
This commit is contained in:
commit
ed082c9869
9 changed files with 229 additions and 197 deletions
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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) {
|
|
@ -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();
|
||||
}
|
||||
}
|
148
src/matrix/storage/idb/Transaction.ts
Normal file
148
src/matrix/storage/idb/Transaction.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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 => {
|
|
@ -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) {
|
|
@ -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() {
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue