feat: complete Ancestor quote bot with production-ready Docker support
This commit is contained in:
705
node_modules/matrix-js-sdk/lib/crypto/store/indexeddb-crypto-store-backend.js
generated
vendored
705
node_modules/matrix-js-sdk/lib/crypto/store/indexeddb-crypto-store-backend.js
generated
vendored
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user