feat: complete Ancestor quote bot with production-ready Docker support

This commit is contained in:
unfunny
2026-09-13 14:14:38 -04:00
parent f2016da05a
commit fe7351a7dc
1145 changed files with 30890 additions and 97684 deletions

View File

@@ -1,4 +1,14 @@
import _defineProperty from "@babel/runtime/helpers/defineProperty";
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.VERSION = exports.Backend = void 0;
exports.upgradeDatabase = upgradeDatabase;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _logger = require("../../logger");
var _utils = require("../../utils");
/*
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
@@ -15,35 +25,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger.js";
import { MigrationState, SESSION_BATCH_SIZE, ACCOUNT_OBJECT_KEY_MIGRATION_STATE } from "./base.js";
import { IndexedDBCryptoStore } from "./indexeddb-crypto-store.js";
const PROFILE_TRANSACTIONS = false;
/**
* Implementation of a CryptoStore which is backed by an existing
* IndexedDB connection. Generally you want IndexedDBCryptoStore
* which connects to the database and defers to one of these.
*
* @internal
*/
export class Backend {
class Backend {
/**
*/
constructor(db) {
_defineProperty(this, "nextTxnId", 0);
this.db = db;
(0, _defineProperty2.default)(this, "nextTxnId", 0);
// make sure we close the db on `onversionchange` - otherwise
// attempts to delete the database will block (and subsequent
// attempts to re-create it will also block).
db.onversionchange = () => {
logger.log(`versionchange for indexeddb ${this.db.name}: closing`);
_logger.logger.log(`versionchange for indexeddb ${this.db.name}: closing`);
db.close();
};
}
async containsData() {
throw Error("Not implemented for Backend");
}
async startup() {
// No work to do, as the startup is done by the caller (e.g IndexedDBCryptoStore)
// by passing us a ready IDBDatabase instance
@@ -54,34 +56,249 @@ export class Backend {
}
/**
* Get data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing outgoing room key request, and if none is found,
* add a new one
*
* Implementation of {@link CryptoStore.getMigrationState}.
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
async getMigrationState() {
let migrationState = MigrationState.NOT_STARTED;
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_ACCOUNT], txn => {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_ACCOUNT);
const getReq = objectStore.get(ACCOUNT_OBJECT_KEY_MIGRATION_STATE);
getReq.onsuccess = () => {
migrationState = getReq.result ?? MigrationState.NOT_STARTED;
};
getOrAddOutgoingRoomKeyRequest(request) {
const requestBody = request.requestBody;
return new Promise((resolve, reject) => {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = reject;
// first see if we already have an entry for this request.
this._getOutgoingRoomKeyRequest(txn, requestBody, existing => {
if (existing) {
// this entry matches the request - return it.
_logger.logger.log(`already have key request outstanding for ` + `${requestBody.room_id} / ${requestBody.session_id}: ` + `not sending another`);
resolve(existing);
return;
}
// we got to the end of the list without finding a match
// - add the new request.
_logger.logger.log(`enqueueing key request for ${requestBody.room_id} / ` + requestBody.session_id);
txn.oncomplete = () => {
resolve(request);
};
const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request);
});
});
return migrationState;
}
/**
* Set data on how much of the libolm to Rust Crypto migration has been done.
* Look for an existing room key request
*
* Implementation of {@link CryptoStore.setMigrationState}.
* @param requestBody - existing request to look for
*
* @returns resolves to the matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found
*/
async setMigrationState(migrationState) {
await this.doTxn("readwrite", [IndexedDBCryptoStore.STORE_ACCOUNT], txn => {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_ACCOUNT);
objectStore.put(migrationState, ACCOUNT_OBJECT_KEY_MIGRATION_STATE);
getOutgoingRoomKeyRequest(requestBody) {
return new Promise((resolve, reject) => {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = reject;
this._getOutgoingRoomKeyRequest(txn, requestBody, existing => {
resolve(existing);
});
});
}
/**
* look for an existing room key request in the db
*
* @internal
* @param txn - database transaction
* @param requestBody - existing request to look for
* @param callback - function to call with the results of the
* search. Either passed a matching
* {@link OutgoingRoomKeyRequest}, or null if
* not found.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
_getOutgoingRoomKeyRequest(txn, requestBody, callback) {
const store = txn.objectStore("outgoingRoomKeyRequests");
const idx = store.index("session");
const cursorReq = idx.openCursor([requestBody.room_id, requestBody.session_id]);
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
// no match found
callback(null);
return;
}
const existing = cursor.value;
if ((0, _utils.deepCompare)(existing.requestBody, requestBody)) {
// got a match
callback(existing);
return;
}
// look at the next entry in the index
cursor.continue();
};
}
/**
* Look for room key requests by state
*
* @param wantedStates - list of acceptable states
*
* @returns resolves to the a
* {@link OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states. If there are multiple
* requests in those states, an arbitrary one is chosen.
*/
getOutgoingRoomKeyRequestByState(wantedStates) {
if (wantedStates.length === 0) {
return Promise.resolve(null);
}
// this is a bit tortuous because we need to make sure we do the lookup
// in a single transaction, to avoid having a race with the insertion
// code.
// index into the wantedStates array
let stateIndex = 0;
let result;
function onsuccess() {
const cursor = this.result;
if (cursor) {
// got a match
result = cursor.value;
return;
}
// try the next state in the list
stateIndex++;
if (stateIndex >= wantedStates.length) {
// no matches
return;
}
const wantedState = wantedStates[stateIndex];
const cursorReq = this.source.openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
}
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
const cursorReq = store.index("state").openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => result);
}
/**
*
* @returns All elements in a given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
return new Promise((resolve, reject) => {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const index = store.index("state");
const request = index.getAll(wantedState);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
let stateIndex = 0;
const results = [];
function onsuccess() {
const cursor = this.result;
if (cursor) {
const keyReq = cursor.value;
if (keyReq.recipients.some(recipient => recipient.userId === userId && recipient.deviceId === deviceId)) {
results.push(keyReq);
}
cursor.continue();
} else {
// try the next state in the list
stateIndex++;
if (stateIndex >= wantedStates.length) {
// no matches
return;
}
const wantedState = wantedStates[stateIndex];
const cursorReq = this.source.openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
}
}
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
const cursorReq = store.index("state").openCursor(wantedState);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => results);
}
/**
* Look for an existing room key request by id and state, and update it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
* @param updates - name/value map of updates to apply
*
* @returns resolves to
* {@link OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
let result = null;
function onsuccess() {
const cursor = this.result;
if (!cursor) {
return;
}
const data = cursor.value;
if (data.state != expectedState) {
_logger.logger.warn(`Cannot update room key request from ${expectedState} ` + `as it was already updated to ${data.state}`);
return;
}
Object.assign(data, updates);
cursor.update(data);
result = data;
}
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => result);
}
/**
* Look for an existing room key request by id and state, and delete it if
* found
*
* @param requestId - ID of request to update
* @param expectedState - state we expect to find the request in
*
* @returns resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
return;
}
const data = cursor.value;
if (data.state != expectedState) {
_logger.logger.warn(`Cannot delete room key request in state ${data.state} ` + `(expected ${expectedState})`);
return;
}
cursor.delete();
};
return promiseifyTxn(txn);
}
// Olm Account
getAccount(txn, func) {
@@ -121,6 +338,10 @@ export class Backend {
}
};
}
storeCrossSigningKeys(txn, keys) {
const objectStore = txn.objectStore("account");
objectStore.put(keys, "crossSigningKeys");
}
storeSecretStorePrivateKey(txn, type, key) {
const objectStore = txn.objectStore("account");
objectStore.put(key, `ssss_cache:${type}`);
@@ -179,6 +400,23 @@ export class Backend {
}
};
}
getAllEndToEndSessions(txn, func) {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
try {
const cursor = getReq.result;
if (cursor) {
func(cursor.value);
cursor.continue();
} else {
func(null);
}
} catch (e) {
abortWithException(txn, e);
}
};
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
const objectStore = txn.objectStore("sessions");
objectStore.put({
@@ -188,62 +426,76 @@ export class Backend {
lastReceivedMessageTs: sessionInfo.lastReceivedMessageTs
});
}
/**
* Fetch a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndSessionsBatch}.
*/
async getEndToEndSessionsBatch() {
const result = [];
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_SESSIONS], txn => {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_SESSIONS);
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
try {
const cursor = getReq.result;
if (cursor) {
result.push(cursor.value);
if (result.length < SESSION_BATCH_SIZE) {
cursor.continue();
}
}
} catch (e) {
abortWithException(txn, e);
}
};
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const txn = this.db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
objectStore.put({
deviceKey,
type,
fixed,
time: Date.now()
});
if (result.length === 0) {
// No sessions left.
return null;
}
await promiseifyTxn(txn);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
let result = null;
const txn = this.db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
const index = objectStore.index("deviceKey");
const req = index.getAll(deviceKey);
req.onsuccess = () => {
const problems = req.result;
if (!problems.length) {
result = null;
return;
}
problems.sort((a, b) => {
return a.time - b.time;
});
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
result = Object.assign({}, problem, {
fixed: lastProblem.fixed
});
return;
}
}
if (lastProblem.fixed) {
result = null;
} else {
result = lastProblem;
}
};
await promiseifyTxn(txn);
return result;
}
/**
* Delete a batch of Olm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndSessionsBatch}.
*
* @internal
*/
async deleteEndToEndSessionsBatch(sessions) {
await this.doTxn("readwrite", [IndexedDBCryptoStore.STORE_SESSIONS], async txn => {
try {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_SESSIONS);
for (const {
deviceKey,
sessionId
} of sessions) {
const req = objectStore.delete([deviceKey, sessionId]);
await new Promise(resolve => {
req.onsuccess = resolve;
});
}
} catch (e) {
abortWithException(txn, e);
}
});
// FIXME: we should probably prune this when devices get deleted
async filterOutNotifiedErrorDevices(devices) {
const txn = this.db.transaction("notified_error_devices", "readwrite");
const objectStore = txn.objectStore("notified_error_devices");
const ret = [];
await Promise.all(devices.map(device => {
return new Promise(resolve => {
const {
userId,
deviceInfo
} = device;
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
getReq.onsuccess = function () {
if (!getReq.result) {
objectStore.put({
userId,
deviceId: deviceInfo.deviceId
});
ret.push(device);
}
resolve();
};
});
}));
return ret;
}
// Inbound group sessions
@@ -284,6 +536,51 @@ export class Backend {
}
};
}
getAllEndToEndInboundGroupSessions(txn, func) {
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
const cursor = getReq.result;
if (cursor) {
try {
func({
senderKey: cursor.value.senderCurve25519Key,
sessionId: cursor.value.sessionId,
sessionData: cursor.value.session
});
} catch (e) {
abortWithException(txn, e);
}
cursor.continue();
} else {
try {
func(null);
} catch (e) {
abortWithException(txn, e);
}
}
};
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions");
const addReq = objectStore.add({
senderCurve25519Key,
sessionId,
session: sessionData
});
addReq.onerror = ev => {
var _addReq$error;
if (((_addReq$error = addReq.error) === null || _addReq$error === void 0 ? void 0 : _addReq$error.name) === "ConstraintError") {
// This stops the error from triggering the txn's onerror
ev.stopPropagation();
// ...and this stops it from aborting the transaction
ev.preventDefault();
_logger.logger.log("Ignoring duplicate inbound group session: " + senderCurve25519Key + " / " + sessionId);
} else {
abortWithException(txn, new Error("Failed to add inbound group session: " + addReq.error));
}
};
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions");
objectStore.put({
@@ -292,89 +589,12 @@ export class Backend {
session: sessionData
});
}
/**
* Count the number of Megolm sessions in the database.
*
* Implementation of {@link CryptoStore.countEndToEndInboundGroupSessions}.
*
* @internal
*/
async countEndToEndInboundGroupSessions() {
let result = 0;
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], txn => {
const sessionStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS);
const countReq = sessionStore.count();
countReq.onsuccess = () => {
result = countReq.result;
};
});
return result;
}
/**
* Fetch a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}.
*/
async getEndToEndInboundGroupSessionsBatch() {
const result = [];
await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], txn => {
const sessionStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS);
const backupStore = txn.objectStore(IndexedDBCryptoStore.STORE_BACKUP);
const getReq = sessionStore.openCursor();
getReq.onsuccess = function () {
try {
const cursor = getReq.result;
if (cursor) {
const backupGetReq = backupStore.get(cursor.key);
backupGetReq.onsuccess = () => {
result.push({
senderKey: cursor.value.senderCurve25519Key,
sessionId: cursor.value.sessionId,
sessionData: cursor.value.session,
needsBackup: backupGetReq.result !== undefined
});
if (result.length < SESSION_BATCH_SIZE) {
cursor.continue();
}
};
}
} catch (e) {
abortWithException(txn, e);
}
};
});
if (result.length === 0) {
// No sessions left.
return null;
}
return result;
}
/**
* Delete a batch of Megolm sessions from the database.
*
* Implementation of {@link CryptoStore.deleteEndToEndInboundGroupSessionsBatch}.
*
* @internal
*/
async deleteEndToEndInboundGroupSessionsBatch(sessions) {
await this.doTxn("readwrite", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], async txn => {
try {
const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS);
for (const {
senderKey,
sessionId
} of sessions) {
const req = objectStore.delete([senderKey, sessionId]);
await new Promise(resolve => {
req.onsuccess = resolve;
});
}
} catch (e) {
abortWithException(txn, e);
}
storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn) {
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
objectStore.put({
senderCurve25519Key,
sessionId,
session: sessionData
});
}
getEndToEndDeviceData(txn, func) {
@@ -388,6 +608,14 @@ export class Backend {
}
};
}
storeEndToEndDeviceData(deviceData, txn) {
const objectStore = txn.objectStore("device_data");
objectStore.put(deviceData, "-");
}
storeEndToEndRoom(roomId, roomInfo, txn) {
const objectStore = txn.objectStore("rooms");
objectStore.put(roomInfo, roomId);
}
getEndToEndRooms(txn, func) {
const rooms = {};
const objectStore = txn.objectStore("rooms");
@@ -406,6 +634,62 @@ export class Backend {
}
};
}
// session backups
getSessionsNeedingBackup(limit) {
return new Promise((resolve, reject) => {
const sessions = [];
const txn = this.db.transaction(["sessions_needing_backup", "inbound_group_sessions"], "readonly");
txn.onerror = reject;
txn.oncomplete = function () {
resolve(sessions);
};
const objectStore = txn.objectStore("sessions_needing_backup");
const sessionStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function () {
const cursor = getReq.result;
if (cursor) {
const sessionGetReq = sessionStore.get(cursor.key);
sessionGetReq.onsuccess = function () {
sessions.push({
senderKey: sessionGetReq.result.senderCurve25519Key,
sessionId: sessionGetReq.result.sessionId,
sessionData: sessionGetReq.result.session
});
};
if (!limit || sessions.length < limit) {
cursor.continue();
}
}
};
});
}
countSessionsNeedingBackup(txn) {
if (!txn) {
txn = this.db.transaction("sessions_needing_backup", "readonly");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return new Promise((resolve, reject) => {
const req = objectStore.count();
req.onerror = reject;
req.onsuccess = () => resolve(req.result);
});
}
async unmarkSessionsNeedingBackup(sessions, txn) {
if (!txn) {
txn = this.db.transaction("sessions_needing_backup", "readwrite");
}
const objectStore = txn.objectStore("sessions_needing_backup");
await Promise.all(sessions.map(session => {
return new Promise((resolve, reject) => {
const req = objectStore.delete([session.senderKey, session.sessionId]);
req.onsuccess = resolve;
req.onerror = reject;
});
}));
}
async markSessionsNeedingBackup(sessions, txn) {
if (!txn) {
txn = this.db.transaction("sessions_needing_backup", "readwrite");
@@ -422,7 +706,82 @@ export class Backend {
});
}));
}
doTxn(mode, stores, func, log = logger) {
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
if (!txn) {
txn = this.db.transaction("shared_history_inbound_group_sessions", "readwrite");
}
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
const req = objectStore.get([roomId]);
req.onsuccess = () => {
const {
sessions
} = req.result || {
sessions: []
};
sessions.push([senderKey, sessionId]);
objectStore.put({
roomId,
sessions
});
};
}
getSharedHistoryInboundGroupSessions(roomId, txn) {
if (!txn) {
txn = this.db.transaction("shared_history_inbound_group_sessions", "readonly");
}
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
const req = objectStore.get([roomId]);
return new Promise((resolve, reject) => {
req.onsuccess = () => {
const {
sessions
} = req.result || {
sessions: []
};
resolve(sessions);
};
req.onerror = reject;
});
}
addParkedSharedHistory(roomId, parkedData, txn) {
if (!txn) {
txn = this.db.transaction("parked_shared_history", "readwrite");
}
const objectStore = txn.objectStore("parked_shared_history");
const req = objectStore.get([roomId]);
req.onsuccess = () => {
const {
parked
} = req.result || {
parked: []
};
parked.push(parkedData);
objectStore.put({
roomId,
parked
});
};
}
takeParkedSharedHistory(roomId, txn) {
if (!txn) {
txn = this.db.transaction("parked_shared_history", "readwrite");
}
const cursorReq = txn.objectStore("parked_shared_history").openCursor(roomId);
return new Promise((resolve, reject) => {
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
resolve([]);
return;
}
const data = cursor.value;
cursor.delete();
resolve(data);
};
cursorReq.onerror = reject;
});
}
doTxn(mode, stores, func, log = _logger.logger) {
let startTime;
let description;
if (PROFILE_TRANSACTIONS) {
@@ -448,6 +807,7 @@ export class Backend {
});
}
}
exports.Backend = Backend;
const DB_MIGRATIONS = [db => {
createDatabase(db);
}, db => {
@@ -492,9 +852,11 @@ const DB_MIGRATIONS = [db => {
}
// Expand as needed.
];
export const VERSION = DB_MIGRATIONS.length;
export function upgradeDatabase(db, oldVersion) {
logger.log(`Upgrading IndexedDBCryptoStore from version ${oldVersion} to ${VERSION}`);
const VERSION = DB_MIGRATIONS.length;
exports.VERSION = VERSION;
function upgradeDatabase(db, oldVersion) {
_logger.logger.log(`Upgrading IndexedDBCryptoStore from version ${oldVersion}` + ` to ${VERSION}`);
DB_MIGRATIONS.forEach((migration, index) => {
if (oldVersion <= index) migration(db);
});
@@ -520,7 +882,7 @@ function abortWithException(txn, e) {
txn._mx_abortexception = e;
try {
txn.abort();
} catch {
} catch (e) {
// sometimes we won't be able to abort the transaction
// (ie. if it's aborted or completed)
}
@@ -530,7 +892,6 @@ function promiseifyTxn(txn) {
txn.oncomplete = () => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
return;
}
resolve(null);
};
@@ -538,7 +899,7 @@ function promiseifyTxn(txn) {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
_logger.logger.log("Error performing indexeddb txn", event);
reject(txn.error);
}
};
@@ -546,7 +907,7 @@ function promiseifyTxn(txn) {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
_logger.logger.log("Error performing indexeddb txn", event);
reject(txn.error);
}
};