2020-08-05 22:08:55 +05:30
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-09-01 04:14:03 +05:30
|
|
|
import {StoreNames} from "../common";
|
2021-08-10 02:26:20 +05:30
|
|
|
import {txnAsPromise} from "./utils";
|
2021-08-10 02:14:07 +05:30
|
|
|
import {StorageError} from "../common";
|
2021-08-11 01:01:03 +05:30
|
|
|
import {Store} from "./Store";
|
2021-09-17 21:51:48 +05:30
|
|
|
import {Storage} from "./Storage";
|
2021-08-11 04:40:55 +05:30
|
|
|
import {SessionStore} from "./stores/SessionStore";
|
2021-08-11 04:58:47 +05:30
|
|
|
import {RoomSummaryStore} from "./stores/RoomSummaryStore";
|
2021-08-12 00:33:06 +05:30
|
|
|
import {InviteStore} from "./stores/InviteStore";
|
2021-08-12 02:03:25 +05:30
|
|
|
import {TimelineEventStore} from "./stores/TimelineEventStore";
|
2021-08-12 02:37:44 +05:30
|
|
|
import {TimelineRelationStore} from "./stores/TimelineRelationStore";
|
2021-08-12 02:52:17 +05:30
|
|
|
import {RoomStateStore} from "./stores/RoomStateStore";
|
2021-08-12 03:29:52 +05:30
|
|
|
import {RoomMemberStore} from "./stores/RoomMemberStore";
|
2021-08-12 04:05:28 +05:30
|
|
|
import {TimelineFragmentStore} from "./stores/TimelineFragmentStore";
|
2021-08-12 04:33:42 +05:30
|
|
|
import {PendingEventStore} from "./stores/PendingEventStore";
|
2021-08-12 05:08:37 +05:30
|
|
|
import {UserIdentityStore} from "./stores/UserIdentityStore";
|
2021-08-12 05:14:38 +05:30
|
|
|
import {DeviceIdentityStore} from "./stores/DeviceIdentityStore";
|
2021-08-12 22:49:09 +05:30
|
|
|
import {OlmSessionStore} from "./stores/OlmSessionStore";
|
2021-08-12 22:54:47 +05:30
|
|
|
import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore";
|
2021-08-12 23:03:05 +05:30
|
|
|
import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore";
|
2021-08-12 23:11:58 +05:30
|
|
|
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore";
|
2021-08-12 23:35:55 +05:30
|
|
|
import {OperationStore} from "./stores/OperationStore";
|
2021-08-13 01:07:47 +05:30
|
|
|
import {AccountDataStore} from "./stores/AccountDataStore";
|
2021-09-17 21:51:48 +05:30
|
|
|
import {LogItem} from "../../../logging/LogItem.js";
|
2021-09-17 21:49:26 +05:30
|
|
|
import {BaseLogger} from "../../../logging/BaseLogger.js";
|
2019-02-05 04:51:50 +05:30
|
|
|
|
2021-09-22 13:52:52 +05:30
|
|
|
export type IDBKey = IDBValidKey | IDBKeyRange;
|
|
|
|
|
2021-09-17 21:51:48 +05:30
|
|
|
class WriteErrorInfo {
|
|
|
|
constructor(
|
|
|
|
public readonly error: StorageError,
|
|
|
|
public readonly refItem: LogItem | undefined,
|
|
|
|
public readonly operationName: string,
|
2021-09-22 13:52:52 +05:30
|
|
|
public readonly keys: IDBKey[] | undefined,
|
2021-09-17 21:51:48 +05:30
|
|
|
) {}
|
|
|
|
}
|
|
|
|
|
2020-04-21 00:56:39 +05:30
|
|
|
export class Transaction {
|
2021-08-13 01:08:44 +05:30
|
|
|
private _txn: IDBTransaction;
|
2021-09-01 04:14:03 +05:30
|
|
|
private _allowedStoreNames: StoreNames[];
|
|
|
|
private _stores: { [storeName in StoreNames]?: any };
|
2021-09-17 21:49:26 +05:30
|
|
|
private _storage: Storage;
|
2021-09-17 21:51:48 +05:30
|
|
|
private _writeErrors: WriteErrorInfo[];
|
2021-08-13 01:08:44 +05:30
|
|
|
|
2021-09-17 21:49:26 +05:30
|
|
|
constructor(txn: IDBTransaction, allowedStoreNames: StoreNames[], storage: Storage) {
|
2019-06-27 02:01:36 +05:30
|
|
|
this._txn = txn;
|
|
|
|
this._allowedStoreNames = allowedStoreNames;
|
2020-09-14 19:14:34 +05:30
|
|
|
this._stores = {};
|
2021-09-17 21:49:26 +05:30
|
|
|
this._storage = storage;
|
|
|
|
this._writeErrors = [];
|
|
|
|
}
|
|
|
|
|
2021-09-18 03:49:16 +05:30
|
|
|
get idbFactory(): IDBFactory {
|
|
|
|
return this._storage.idbFactory;
|
|
|
|
}
|
|
|
|
|
2021-09-17 21:49:26 +05:30
|
|
|
get IDBKeyRange(): typeof IDBKeyRange {
|
|
|
|
return this._storage.IDBKeyRange;
|
|
|
|
}
|
|
|
|
|
2021-09-29 15:19:58 +05:30
|
|
|
get databaseName(): string {
|
|
|
|
return this._storage.databaseName;
|
|
|
|
}
|
|
|
|
|
2021-09-17 21:49:26 +05:30
|
|
|
get logger(): BaseLogger {
|
|
|
|
return this._storage.logger;
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-02-05 04:51:50 +05:30
|
|
|
|
2021-09-01 04:14:03 +05:30
|
|
|
_idbStore(name: StoreNames): Store<any> {
|
2019-06-27 02:01:36 +05:30
|
|
|
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.`);
|
|
|
|
}
|
2021-06-02 16:01:13 +05:30
|
|
|
return new Store(this._txn.objectStore(name), this);
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-02-05 04:51:50 +05:30
|
|
|
|
2021-09-06 16:31:32 +05:30
|
|
|
_store<T>(name: StoreNames, mapStore: (idbStore: Store<any>) => T): T {
|
2019-06-27 02:01:36 +05:30
|
|
|
if (!this._stores[name]) {
|
|
|
|
const idbStore = this._idbStore(name);
|
|
|
|
this._stores[name] = mapStore(idbStore);
|
|
|
|
}
|
|
|
|
return this._stores[name];
|
|
|
|
}
|
2019-02-07 05:50:27 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get session(): SessionStore {
|
2021-09-29 15:19:58 +05:30
|
|
|
return this._store(StoreNames.session, idbStore => new SessionStore(idbStore, this._storage.localStorage));
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-02-05 04:51:50 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get roomSummary(): RoomSummaryStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.roomSummary, idbStore => new RoomSummaryStore(idbStore));
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2021-05-04 17:04:42 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get archivedRoomSummary(): RoomSummaryStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.archivedRoomSummary, idbStore => new RoomSummaryStore(idbStore));
|
2021-05-04 17:04:42 +05:30
|
|
|
}
|
2019-02-11 01:55:29 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get invites(): InviteStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.invites, idbStore => new InviteStore(idbStore));
|
2021-04-20 16:32:50 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get timelineFragments(): TimelineFragmentStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.timelineFragments, idbStore => new TimelineFragmentStore(idbStore));
|
2019-05-12 23:54:06 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get timelineEvents(): TimelineEventStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.timelineEvents, idbStore => new TimelineEventStore(idbStore));
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-02-11 01:55:29 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get timelineRelations(): TimelineRelationStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.timelineRelations, idbStore => new TimelineRelationStore(idbStore));
|
2021-06-03 20:14:35 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get roomState(): RoomStateStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.roomState, idbStore => new RoomStateStore(idbStore));
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-02-11 01:55:29 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get roomMembers(): RoomMemberStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.roomMembers, idbStore => new RoomMemberStore(idbStore));
|
2020-06-27 02:56:24 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get pendingEvents(): PendingEventStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.pendingEvents, idbStore => new PendingEventStore(idbStore));
|
2019-07-27 14:10:56 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get userIdentities(): UserIdentityStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.userIdentities, idbStore => new UserIdentityStore(idbStore));
|
2020-08-31 18:08:03 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get deviceIdentities(): DeviceIdentityStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.deviceIdentities, idbStore => new DeviceIdentityStore(idbStore));
|
2020-08-31 18:08:03 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get olmSessions(): OlmSessionStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.olmSessions, idbStore => new OlmSessionStore(idbStore));
|
2020-09-01 21:29:59 +05:30
|
|
|
}
|
2020-09-02 17:54:38 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get inboundGroupSessions(): InboundGroupSessionStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.inboundGroupSessions, idbStore => new InboundGroupSessionStore(idbStore));
|
2020-09-02 17:54:38 +05:30
|
|
|
}
|
2020-09-03 21:19:20 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get outboundGroupSessions(): OutboundGroupSessionStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.outboundGroupSessions, idbStore => new OutboundGroupSessionStore(idbStore));
|
2020-09-03 21:19:20 +05:30
|
|
|
}
|
2020-09-04 19:01:00 +05:30
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get groupSessionDecryptions(): GroupSessionDecryptionStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.groupSessionDecryptions, idbStore => new GroupSessionDecryptionStore(idbStore));
|
2020-09-04 19:01:00 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get operations(): OperationStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.operations, idbStore => new OperationStore(idbStore));
|
2020-09-11 18:10:05 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 01:08:44 +05:30
|
|
|
get accountData(): AccountDataStore {
|
2021-09-01 04:14:03 +05:30
|
|
|
return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore));
|
2020-09-17 14:09:51 +05:30
|
|
|
}
|
|
|
|
|
2021-09-17 21:53:31 +05:30
|
|
|
async complete(log?: LogItem): Promise<void> {
|
|
|
|
try {
|
|
|
|
await txnAsPromise(this._txn);
|
|
|
|
} catch (err) {
|
|
|
|
if (this._writeErrors.length) {
|
|
|
|
this._logWriteErrors(log);
|
|
|
|
throw this._writeErrors[0].error;
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getCause(error: Error) {
|
|
|
|
if (error instanceof StorageError) {
|
|
|
|
if (error.errcode === "AbortError" && this._writeErrors.length) {
|
|
|
|
return this._writeErrors[0].error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return error;
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-02-05 04:51:50 +05:30
|
|
|
|
2021-09-17 21:53:31 +05:30
|
|
|
abort(log?: LogItem): void {
|
2021-02-12 01:38:06 +05:30
|
|
|
// TODO: should we wrap the exception in a StorageError?
|
2021-09-17 21:53:31 +05:30
|
|
|
try {
|
|
|
|
this._txn.abort();
|
|
|
|
} catch (abortErr) {
|
|
|
|
log?.set("couldNotAbortTxn", true);
|
|
|
|
}
|
|
|
|
if (this._writeErrors.length) {
|
|
|
|
this._logWriteErrors(log);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-22 13:52:52 +05:30
|
|
|
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {
|
2021-09-17 21:51:48 +05:30
|
|
|
// don't log subsequent `AbortError`s
|
|
|
|
if (error.errcode !== "AbortError" || this._writeErrors.length === 0) {
|
2021-09-22 13:52:52 +05:30
|
|
|
this._writeErrors.push(new WriteErrorInfo(error, refItem, operationName, keys));
|
2021-09-17 21:51:48 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private _logWriteErrors(parentItem: LogItem | undefined) {
|
|
|
|
const callback = errorGroupItem => {
|
|
|
|
// we don't have context when there is no parentItem, so at least log stores
|
|
|
|
if (!parentItem) {
|
|
|
|
errorGroupItem.set("allowedStoreNames", this._allowedStoreNames);
|
|
|
|
}
|
|
|
|
for (const info of this._writeErrors) {
|
2021-09-22 13:52:52 +05:30
|
|
|
errorGroupItem.wrap({l: info.operationName, id: info.keys}, item => {
|
2021-09-17 21:51:48 +05:30
|
|
|
if (info.refItem) {
|
|
|
|
item.refDetached(info.refItem);
|
|
|
|
}
|
|
|
|
item.catch(info.error);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const label = `${this._writeErrors.length} storage write operation(s) failed`;
|
|
|
|
if (parentItem) {
|
|
|
|
parentItem.wrap(label, callback);
|
|
|
|
} else {
|
|
|
|
this.logger.run(label, callback);
|
|
|
|
}
|
2019-06-27 02:01:36 +05:30
|
|
|
}
|
2019-05-12 23:54:06 +05:30
|
|
|
}
|