1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-06 12:02:40 +03:00

Convert crypto/store/* to Typescript

This commit is contained in:
Michael Telatynski
2021-07-24 11:35:30 +01:00
parent dc154a00a8
commit 011a2acfd2
18 changed files with 873 additions and 520 deletions

View File

@@ -74,3 +74,11 @@ export enum HistoryVisibility {
Shared = "shared",
WorldReadable = "world_readable",
}
// XXX move to OlmDevice when converted
export interface InboundGroupSessionData {
room_id: string; // eslint-disable-line camelcase
session: string;
keysClaimed: Record<string, string>;
forwardingCurve25519KeyChain: string[];
}

View File

@@ -146,10 +146,10 @@ import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@
import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "./@types/spaces";
import { IPusher, IPusherRequest, IPushRules, PushRuleAction, PushRuleKind, RuleId } from "./@types/PushRules";
import { IThreepid } from "./@types/threepids";
import { CryptoStore } from "./crypto/store/base";
export type Store = IStore;
export type SessionStore = WebStorageSessionStore;
export type CryptoStore = MemoryCryptoStore | LocalStorageCryptoStore | IndexedDBCryptoStore;
export type Callback = (err: Error | any | null, data?: any) => void;
export type ResetTimelineCallback = (roomId: string) => boolean;

View File

@@ -28,10 +28,11 @@ import { decryptAES, encryptAES } from './aes';
import { PkSigning } from "@matrix-org/olm";
import { DeviceInfo } from "./deviceinfo";
import { SecretStorage } from "./SecretStorage";
import { CryptoStore, ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
import { OlmDevice } from "./OlmDevice";
import { ICryptoCallbacks } from "../matrix";
import { ISignatures } from "../@types/signed";
import { CryptoStore } from "./store/base";
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
@@ -47,6 +48,12 @@ export interface ICacheCallbacks {
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>;
}
export interface ICrossSigningInfo {
keys: Record<string, ICrossSigningKey>;
firstUse: boolean;
crossSigningVerifiedBefore: boolean;
}
export class CrossSigningInfo extends EventEmitter {
public keys: Record<string, ICrossSigningKey> = {};
public firstUse = true;
@@ -75,7 +82,7 @@ export class CrossSigningInfo extends EventEmitter {
super();
}
public static fromStorage(obj: object, userId: string): CrossSigningInfo {
public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo {
const res = new CrossSigningInfo(userId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
@@ -85,7 +92,7 @@ export class CrossSigningInfo extends EventEmitter {
return res;
}
public toStorage(): object {
public toStorage(): ICrossSigningInfo {
return {
keys: this.keys,
firstUse: this.firstUse,

View File

@@ -24,12 +24,13 @@ import { EventEmitter } from 'events';
import { logger } from '../logger';
import { DeviceInfo, IDevice } from './deviceinfo';
import { CrossSigningInfo } from './CrossSigning';
import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning';
import * as olmlib from './olmlib';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { chunkPromises, defer, IDeferred, sleep } from '../utils';
import { MatrixClient, CryptoStore } from "../client";
import { MatrixClient } from "../client";
import { OlmDevice } from "./OlmDevice";
import { CryptoStore } from "./store/base";
/* State transition diagram for DeviceList.deviceTrackingStatus
*
@@ -52,7 +53,7 @@ import { OlmDevice } from "./OlmDevice";
*/
// constants for DeviceList.deviceTrackingStatus
enum TrackingStatus {
export enum TrackingStatus {
NotTracked,
PendingDownload,
DownloadInProgress,
@@ -65,32 +66,22 @@ export type DeviceInfoMap = Record<string, Record<string, DeviceInfo>>;
* @alias module:crypto/DeviceList
*/
export class DeviceList extends EventEmitter {
// userId -> {
// deviceId -> {
// [device info]
// }
// }
private devices: Record<string, Record<string, IDevice>> = {};
private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {};
// userId -> {
// [key info]
// }
public crossSigningInfo: Record<string, object> = {};
public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {};
// map of identity keys to the user who owns it
private userByIdentityKey: Record<string, string> = {};
// which users we are tracking device status for.
// userId -> TRACKING_STATUS_*
private deviceTrackingStatus: Record<string, TrackingStatus> = {}; // loaded from storage in load()
private deviceTrackingStatus: { [userId: string]: TrackingStatus } = {}; // loaded from storage in load()
// The 'next_batch' sync token at the point the data was written,
// ie. a token representing the point immediately after the
// moment represented by the snapshot in the db.
private syncToken: string = null;
// userId -> promise
private keyDownloadsInProgressByUser: Record<string, Promise<void>> = {};
private keyDownloadsInProgressByUser: { [userId: string]: Promise<void> } = {};
// Set whenever changes are made other than setting the sync token
private dirty = false;
@@ -375,7 +366,7 @@ export class DeviceList extends EventEmitter {
return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId);
}
public storeCrossSigningForUser(userId: string, info: CrossSigningInfo): void {
public storeCrossSigningForUser(userId: string, info: ICrossSigningInfo): void {
this.crossSigningInfo[userId] = info;
this.dirty = true;
}
@@ -603,7 +594,7 @@ export class DeviceList extends EventEmitter {
}
}
public setRawStoredCrossSigningForUser(userId: string, info: object): void {
public setRawStoredCrossSigningForUser(userId: string, info: ICrossSigningInfo): void {
this.crossSigningInfo[userId] = info;
}
@@ -864,9 +855,7 @@ class DeviceListUpdateSerialiser {
crossSigning.setKeys(crossSigningResponse);
this.deviceList.setRawStoredCrossSigningForUser(
userId, crossSigning.toStorage(),
);
this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage());
// NB. Unlike most events in the js-sdk, this one is internal to the
// js-sdk and is not re-emitted

View File

@@ -949,7 +949,7 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) {
* data stored in the session store about an inbound group session
*
* @typedef {Object} InboundGroupSessionData
* @property {string} room_Id
* @property {string} room_id
* @property {string} session pickled Olm.InboundGroupSession
* @property {Object<string, string>} keysClaimed
* @property {Array<string>} forwardingCurve25519KeyChain Devices involved in forwarding

View File

@@ -15,9 +15,9 @@ limitations under the License.
*/
import { logger } from '../logger';
import { CryptoStore, MatrixClient } from "../client";
import { MatrixClient } from "../client";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index";
import { OutgoingRoomKeyRequest } from './store/base';
import { CryptoStore, OutgoingRoomKeyRequest } from './store/base';
import { EventType } from "../@types/event";
/**
@@ -263,9 +263,8 @@ export class OutgoingRoomKeyRequestManager {
'deleting unnecessary room key request for ' +
stringifyRequestBody(requestBody),
);
return this.cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Unsent,
);
return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent)
.then(); // match Promise<void> signature
case RoomKeyRequestState.Sent: {
// send a cancellation.
@@ -325,7 +324,7 @@ export class OutgoingRoomKeyRequestManager {
* @return {Promise} resolves to a list of all the
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
*/
public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): OutgoingRoomKeyRequest[] {
public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): Promise<OutgoingRoomKeyRequest[]> {
return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]);
}
@@ -415,7 +414,7 @@ export class OutgoingRoomKeyRequestManager {
}
// given a RoomKeyRequest, send it and update the request record
private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise<void> {
private async sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise<void> {
logger.log(
`Requesting keys for ${stringifyRequestBody(req.requestBody)}` +
` from ${stringifyRecipientList(req.recipients)}` +
@@ -429,19 +428,19 @@ export class OutgoingRoomKeyRequestManager {
body: req.requestBody,
};
return this.sendMessageToDevices(
requestMessage, req.recipients, req.requestTxnId || req.requestId,
).then(() => {
return this.cryptoStore.updateOutgoingRoomKeyRequest(
await this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId);
await this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Unsent,
{ state: RoomKeyRequestState.Sent },
);
});
}
// Given a RoomKeyRequest, cancel it and delete the request record unless
// andResend is set, in which case transition to UNSENT.
private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise<void> {
private async sendOutgoingRoomKeyRequestCancellation(
req: OutgoingRoomKeyRequest,
andResend = false,
): Promise<void> {
logger.log(
`Sending cancellation for key request for ` +
`${stringifyRequestBody(req.requestBody)} to ` +
@@ -455,21 +454,19 @@ export class OutgoingRoomKeyRequestManager {
request_id: req.requestId,
};
return this.sendMessageToDevices(
requestMessage, req.recipients, req.cancellationTxnId,
).then(() => {
await this.sendMessageToDevices(requestMessage, req.recipients, req.cancellationTxnId);
if (andResend) {
// We want to resend, so transition to UNSENT
return this.cryptoStore.updateOutgoingRoomKeyRequest(
await this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId,
RoomKeyRequestState.CancellationPendingAndWillResend,
{ state: RoomKeyRequestState.Unsent },
);
}
return this.cryptoStore.deleteOutgoingRoomKeyRequest(
} else {
await this.cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.CancellationPending,
);
});
}
}
// send a RoomKeyRequest to a list of recipients

View File

@@ -20,8 +20,8 @@ limitations under the License.
* Manages the list of encrypted rooms
*/
import { CryptoStore } from './store/base';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { CryptoStore } from "../client";
/* eslint-disable camelcase */
export interface IRoomEncryption {

View File

@@ -61,7 +61,7 @@ interface IBlockedMap {
};
}
interface IOlmDevice<T = DeviceInfo> {
export interface IOlmDevice<T = DeviceInfo> {
userId: string;
deviceInfo: T;
}

View File

@@ -59,12 +59,13 @@ import { IStore } from "../store";
import { Room } from "../models/room";
import { RoomMember } from "../models/room-member";
import { MatrixEvent } from "../models/event";
import { MatrixClient, IKeysUploadResponse, SessionStore, CryptoStore, ISignedKey } from "../client";
import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey } from "../client";
import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base";
import type { IRoomEncryption, RoomList } from "./RoomList";
import { IRecoveryKey, IEncryptedEventInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup";
import { ISyncStateData } from "../sync";
import { CryptoStore } from "./store/base";
const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -1410,9 +1411,7 @@ export class Crypto extends EventEmitter {
crossSigning.updateCrossSigningVerifiedBefore(
this.checkUserTrust(userId).isCrossSigningVerified(),
);
this.deviceList.setRawStoredCrossSigningForUser(
userId, crossSigning.toStorage(),
);
this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage());
}
this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId));

View File

@@ -1,5 +1,33 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { TrackingStatus } from "../DeviceList";
import { IRoomEncryption } from "../RoomList";
import { IDevice } from "../deviceinfo";
import { ICrossSigningInfo } from "../CrossSigning";
import { PrefixedLogger } from "../../logger";
import { InboundGroupSessionData } from "../../@types/partials";
import { IEncryptedPayload } from "../aes";
/**
* Internal module. Defintions for storage for the crypto module
* Internal module. Definitions for storage for the crypto module
*
* @module
*/
@@ -9,9 +37,141 @@
*
* @interface CryptoStore
*/
export interface CryptoStore {
startup(): Promise<CryptoStore>;
deleteAllData(): Promise<void>;
getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest>;
getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null>;
getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null>;
getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]>;
getOutgoingRoomKeyRequestsByTarget(
userId: string,
deviceId: string,
wantedStates: number[],
): Promise<OutgoingRoomKeyRequest[]>;
updateOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
updates: Partial<OutgoingRoomKeyRequest>,
): Promise<OutgoingRoomKeyRequest | null>;
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
// Olm Account
getAccount(txn: unknown, func: (accountPickle: string) => void);
storeAccount(txn: unknown, accountPickle: string): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void;
getSecretStorePrivateKey<T>(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): void;
storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void;
storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void;
// Olm Sessions
countEndToEndSessions(txn: unknown, func: (count: number) => void): void;
getEndToEndSession(
deviceKey: string,
sessionId: string,
txn: unknown,
func: (session: ISessionInfo) => void,
): void;
getEndToEndSessions(
deviceKey: string,
txn: unknown,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void;
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void;
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]>;
// Inbound Group Sessions
getEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
txn: unknown,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void;
getAllEndToEndInboundGroupSessions(
txn: unknown,
func: (session: ISession | null) => void,
): void;
addEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: unknown,
): void;
storeEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: unknown,
): void;
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key: string,
sessionId: string,
sessionData: IWithheld,
txn: unknown,
): void;
// Device Data
getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void;
storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void;
storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void;
getEndToEndRooms(txn: unknown, func: (rooms: Record<string, IRoomEncryption>) => void): void;
getSessionsNeedingBackup(limit: number): Promise<ISession[]>;
countSessionsNeedingBackup(txn?: unknown): Promise<number>;
unmarkSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise<void>;
markSessionsNeedingBackup(sessions: ISession[], txn?: unknown): Promise<void>;
addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string, txn?: unknown): void;
getSharedHistoryInboundGroupSessions(
roomId: string,
txn?: IDBTransaction,
): Promise<[senderKey: string, sessionId: string][]>;
// Session key backups
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T, log?: PrefixedLogger): Promise<T>;
}
export type Mode = "readonly" | "readwrite";
export interface ISession {
senderKey: string;
sessionId: string;
sessionData?: InboundGroupSessionData;
}
export interface ISessionInfo {
deviceKey?: string;
sessionId?: string;
session?: string;
lastReceivedMessageTs?: number;
}
export interface IDeviceData {
devices: {
[ userId: string ]: {
[ deviceId: string ]: IDevice;
};
};
trackingStatus: {
[ userId: string ]: TrackingStatus;
};
crossSigningInfo?: Record<string, ICrossSigningInfo>;
syncToken?: string;
}
export interface IProblem {
type: string;
fixed: boolean;
time: number;
}
export interface IWithheld {
// eslint-disable-next-line camelcase
room_id: string;
code: string;
reason: string;
}
/**
* Represents an outgoing room key request

View File

@@ -1,7 +1,5 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,8 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from '../../logger';
import { logger, PrefixedLogger } from '../../logger';
import * as utils from "../../utils";
import {
CryptoStore,
IDeviceData,
IProblem,
ISession,
ISessionInfo,
IWithheld,
Mode,
OutgoingRoomKeyRequest,
} from "./base";
import { IRoomKeyRequestBody } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../../@types/partials";
import { IEncryptedPayload } from "../aes";
export const VERSION = 10;
const PROFILE_TRANSACTIONS = false;
@@ -29,18 +43,17 @@ const PROFILE_TRANSACTIONS = false;
*
* @implements {module:crypto/store/base~CryptoStore}
*/
export class Backend {
export class Backend implements CryptoStore {
private nextTxnId = 0;
/**
* @param {IDBDatabase} db
*/
constructor(db) {
this._db = db;
this._nextTxnId = 0;
constructor(private db: IDBDatabase) {
// 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 = (ev) => {
db.onversionchange = () => {
logger.log(`versionchange for indexeddb ${this._dbName}: closing`);
db.close();
};
@@ -56,11 +69,11 @@ export class Backend {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getOrAddOutgoingRoomKeyRequest(request) {
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
const requestBody = request.requestBody;
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = reject;
// first see if we already have an entry for this request.
@@ -99,9 +112,9 @@ export class Backend {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
* not found
*/
getOutgoingRoomKeyRequest(requestBody) {
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = reject;
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
@@ -122,7 +135,12 @@ export class Backend {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
* not found.
*/
_getOutgoingRoomKeyRequest(txn, requestBody, callback) {
// eslint-disable-next-line @typescript-eslint/naming-convention
private _getOutgoingRoomKeyRequest(
txn: IDBTransaction,
requestBody: IRoomKeyRequestBody,
callback: (req: OutgoingRoomKeyRequest | null) => void,
): void {
const store = txn.objectStore("outgoingRoomKeyRequests");
const idx = store.index("session");
@@ -131,8 +149,8 @@ export class Backend {
requestBody.session_id,
]);
cursorReq.onsuccess = (ev) => {
const cursor = ev.target.result;
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
// no match found
callback(null);
@@ -162,7 +180,7 @@ export class Backend {
* there are no pending requests in those states. If there are multiple
* requests in those states, an arbitrary one is chosen.
*/
getOutgoingRoomKeyRequestByState(wantedStates) {
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
if (wantedStates.length === 0) {
return Promise.resolve(null);
}
@@ -195,7 +213,7 @@ export class Backend {
cursorReq.onsuccess = onsuccess;
}
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
@@ -210,19 +228,23 @@ export class Backend {
* @param {Number} wantedState
* @return {Promise<Array<*>>} All elements in a given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const index = store.index("state");
const request = index.getAll(wantedState);
request.onsuccess = (ev) => resolve(ev.target.result);
request.onerror = (ev) => reject(ev.target.error);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
public getOutgoingRoomKeyRequestsByTarget(
userId: string,
deviceId: string,
wantedStates: number[],
): Promise<OutgoingRoomKeyRequest[]> {
let stateIndex = 0;
const results = [];
@@ -248,7 +270,7 @@ export class Backend {
}
}
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
const txn = this.db.transaction("outgoingRoomKeyRequests", "readonly");
const store = txn.objectStore("outgoingRoomKeyRequests");
const wantedState = wantedStates[stateIndex];
@@ -270,7 +292,11 @@ export class Backend {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
public updateOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
updates: Partial<OutgoingRoomKeyRequest>,
): Promise<OutgoingRoomKeyRequest | null> {
let result = null;
function onsuccess(ev) {
@@ -291,9 +317,8 @@ export class Backend {
result = data;
}
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
const cursorReq = txn.objectStore("outgoingRoomKeyRequests")
.openCursor(requestId);
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
cursorReq.onsuccess = onsuccess;
return promiseifyTxn(txn).then(() => result);
}
@@ -307,12 +332,14 @@ export class Backend {
*
* @returns {Promise} 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 = (ev) => {
const cursor = ev.target.result;
public deleteOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
): Promise<OutgoingRoomKeyRequest | null> {
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
cursorReq.onsuccess = () => {
const cursor = cursorReq.result;
if (!cursor) {
return;
}
@@ -326,12 +353,12 @@ export class Backend {
}
cursor.delete();
};
return promiseifyTxn(txn);
return promiseifyTxn<OutgoingRoomKeyRequest | null>(txn);
}
// Olm Account
getAccount(txn, func) {
public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void): void {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get("-");
getReq.onsuccess = function() {
@@ -343,12 +370,12 @@ export class Backend {
};
}
storeAccount(txn, newData) {
public storeAccount(txn: IDBTransaction, accountPickle: string): void {
const objectStore = txn.objectStore("account");
objectStore.put(newData, "-");
objectStore.put(accountPickle, "-");
}
getCrossSigningKeys(txn, func) {
public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey>) => void): void {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get("crossSigningKeys");
getReq.onsuccess = function() {
@@ -360,7 +387,11 @@ export class Backend {
};
}
getSecretStorePrivateKey(txn, func, type) {
public getSecretStorePrivateKey<T>(
txn: IDBTransaction,
func: (key: IEncryptedPayload | null) => T,
type: string,
): void {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get(`ssss_cache:${type}`);
getReq.onsuccess = function() {
@@ -372,19 +403,19 @@ export class Backend {
};
}
storeCrossSigningKeys(txn, keys) {
public storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void {
const objectStore = txn.objectStore("account");
objectStore.put(keys, "crossSigningKeys");
}
storeSecretStorePrivateKey(txn, type, key) {
public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void {
const objectStore = txn.objectStore("account");
objectStore.put(key, `ssss_cache:${type}`);
}
// Olm Sessions
countEndToEndSessions(txn, func) {
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
const objectStore = txn.objectStore("sessions");
const countReq = objectStore.count();
countReq.onsuccess = function() {
@@ -396,7 +427,11 @@ export class Backend {
};
}
getEndToEndSessions(deviceKey, txn, func) {
public getEndToEndSessions(
deviceKey: string,
txn: IDBTransaction,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void {
const objectStore = txn.objectStore("sessions");
const idx = objectStore.index("deviceKey");
const getReq = idx.openCursor(deviceKey);
@@ -419,7 +454,12 @@ export class Backend {
};
}
getEndToEndSession(deviceKey, sessionId, txn, func) {
public getEndToEndSession(
deviceKey: string,
sessionId: string,
txn: IDBTransaction,
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
): void {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.get([deviceKey, sessionId]);
getReq.onsuccess = function() {
@@ -438,7 +478,7 @@ export class Backend {
};
}
getAllEndToEndSessions(txn, func) {
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
@@ -456,7 +496,12 @@ export class Backend {
};
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
public storeEndToEndSession(
deviceKey: string,
sessionId: string,
sessionInfo: ISessionInfo,
txn: IDBTransaction,
): void {
const objectStore = txn.objectStore("sessions");
objectStore.put({
deviceKey,
@@ -466,8 +511,8 @@ export class Backend {
});
}
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const txn = this._db.transaction("session_problems", "readwrite");
public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
const txn = this.db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
objectStore.put({
deviceKey,
@@ -478,13 +523,13 @@ export class Backend {
return promiseifyTxn(txn);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
let result;
const txn = this._db.transaction("session_problems", "readwrite");
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 = (event) => {
req.onsuccess = () => {
const problems = req.result;
if (!problems.length) {
result = null;
@@ -511,14 +556,14 @@ export class Backend {
}
// FIXME: we should probably prune this when devices get deleted
async filterOutNotifiedErrorDevices(devices) {
const txn = this._db.transaction("notified_error_devices", "readwrite");
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
const txn = this.db.transaction("notified_error_devices", "readwrite");
const objectStore = txn.objectStore("notified_error_devices");
const ret = [];
const ret: IOlmDevice[] = [];
await Promise.all(devices.map((device) => {
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
const { userId, deviceInfo } = device;
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
getReq.onsuccess = function() {
@@ -536,9 +581,14 @@ export class Backend {
// Inbound group sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
let session = false;
let withheld = false;
public getEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
txn: IDBTransaction,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void {
let session: InboundGroupSessionData | boolean = false;
let withheld: IWithheld | boolean = false;
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
getReq.onsuccess = function() {
@@ -549,7 +599,7 @@ export class Backend {
session = null;
}
if (withheld !== false) {
func(session, withheld);
func(session as InboundGroupSessionData, withheld as IWithheld);
}
} catch (e) {
abortWithException(txn, e);
@@ -566,7 +616,7 @@ export class Backend {
withheld = null;
}
if (session !== false) {
func(session, withheld);
func(session as InboundGroupSessionData, withheld as IWithheld);
}
} catch (e) {
abortWithException(txn, e);
@@ -574,7 +624,7 @@ export class Backend {
};
}
getAllEndToEndInboundGroupSessions(txn, func) {
public getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void {
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
@@ -600,7 +650,12 @@ export class Backend {
};
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
public addEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: IDBTransaction,
): void {
const objectStore = txn.objectStore("inbound_group_sessions");
const addReq = objectStore.add({
senderCurve25519Key, sessionId, session: sessionData,
@@ -623,23 +678,31 @@ export class Backend {
};
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
public storeEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: IDBTransaction,
): void {
const objectStore = txn.objectStore("inbound_group_sessions");
objectStore.put({
senderCurve25519Key, sessionId, session: sessionData,
});
}
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
public storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key: string,
sessionId: string,
sessionData: IWithheld,
txn: IDBTransaction,
): void {
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
objectStore.put({
senderCurve25519Key, sessionId, session: sessionData,
});
}
getEndToEndDeviceData(txn, func) {
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
const objectStore = txn.objectStore("device_data");
const getReq = objectStore.get("-");
getReq.onsuccess = function() {
@@ -651,24 +714,24 @@ export class Backend {
};
}
storeEndToEndDeviceData(deviceData, txn) {
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void {
const objectStore = txn.objectStore("device_data");
objectStore.put(deviceData, "-");
}
storeEndToEndRoom(roomId, roomInfo, txn) {
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void {
const objectStore = txn.objectStore("rooms");
objectStore.put(roomInfo, roomId);
}
getEndToEndRooms(txn, func) {
public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void {
const rooms = {};
const objectStore = txn.objectStore("rooms");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
const cursor = getReq.result;
if (cursor) {
rooms[cursor.key] = cursor.value;
rooms[cursor.key as string] = cursor.value;
cursor.continue();
} else {
try {
@@ -682,11 +745,11 @@ export class Backend {
// session backups
getSessionsNeedingBackup(limit) {
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
return new Promise((resolve, reject) => {
const sessions = [];
const txn = this._db.transaction(
const txn = this.db.transaction(
["sessions_needing_backup", "inbound_group_sessions"],
"readonly",
);
@@ -716,9 +779,9 @@ export class Backend {
});
}
countSessionsNeedingBackup(txn) {
public countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number> {
if (!txn) {
txn = this._db.transaction("sessions_needing_backup", "readonly");
txn = this.db.transaction("sessions_needing_backup", "readonly");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return new Promise((resolve, reject) => {
@@ -728,12 +791,12 @@ export class Backend {
});
}
unmarkSessionsNeedingBackup(sessions, txn) {
public async unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
if (!txn) {
txn = this._db.transaction("sessions_needing_backup", "readwrite");
txn = this.db.transaction("sessions_needing_backup", "readwrite");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return Promise.all(sessions.map((session) => {
await Promise.all(sessions.map((session) => {
return new Promise((resolve, reject) => {
const req = objectStore.delete([session.senderKey, session.sessionId]);
req.onsuccess = resolve;
@@ -742,12 +805,12 @@ export class Backend {
}));
}
markSessionsNeedingBackup(sessions, txn) {
public async markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
if (!txn) {
txn = this._db.transaction("sessions_needing_backup", "readwrite");
txn = this.db.transaction("sessions_needing_backup", "readwrite");
}
const objectStore = txn.objectStore("sessions_needing_backup");
return Promise.all(sessions.map((session) => {
await Promise.all(sessions.map((session) => {
return new Promise((resolve, reject) => {
const req = objectStore.put({
senderCurve25519Key: session.senderKey,
@@ -759,9 +822,14 @@ export class Backend {
}));
}
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
public addSharedHistoryInboundGroupSession(
roomId: string,
senderKey: string,
sessionId: string,
txn?: IDBTransaction,
): void {
if (!txn) {
txn = this._db.transaction(
txn = this.db.transaction(
"shared_history_inbound_group_sessions", "readwrite",
);
}
@@ -774,9 +842,12 @@ export class Backend {
};
}
getSharedHistoryInboundGroupSessions(roomId, txn) {
public getSharedHistoryInboundGroupSessions(
roomId: string,
txn?: IDBTransaction,
): Promise<[senderKey: string, sessionId: string][]> {
if (!txn) {
txn = this._db.transaction(
txn = this.db.transaction(
"shared_history_inbound_group_sessions", "readonly",
);
}
@@ -791,16 +862,21 @@ export class Backend {
});
}
doTxn(mode, stores, func, log = logger) {
public doTxn<T>(
mode: Mode,
stores: Iterable<string>,
func: (txn: IDBTransaction) => T,
log: PrefixedLogger = logger,
): Promise<T> {
let startTime;
let description;
if (PROFILE_TRANSACTIONS) {
const txnId = this._nextTxnId++;
const txnId = this.nextTxnId++;
startTime = Date.now();
description = `${mode} crypto store transaction ${txnId} in ${stores}`;
log.debug(`Starting ${description}`);
}
const txn = this._db.transaction(stores, mode);
const txn = this.db.transaction(stores, mode);
const promise = promiseifyTxn(txn);
const result = func(txn);
if (PROFILE_TRANSACTIONS) {
@@ -818,7 +894,7 @@ export class Backend {
}
}
export function upgradeDatabase(db, oldVersion) {
export function upgradeDatabase(db: IDBDatabase, oldVersion: number): void {
logger.log(
`Upgrading IndexedDBCryptoStore from version ${oldVersion}`
+ ` to ${VERSION}`,
@@ -874,7 +950,7 @@ export function upgradeDatabase(db, oldVersion) {
// Expand as needed.
}
function createDatabase(db) {
function createDatabase(db: IDBDatabase): void {
const outgoingRoomKeyRequestsStore =
db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" });
@@ -887,15 +963,19 @@ function createDatabase(db) {
outgoingRoomKeyRequestsStore.createIndex("state", "state");
}
interface IWrappedIDBTransaction extends IDBTransaction {
_mx_abortexception: Error; // eslint-disable-line camelcase
}
/*
* Aborts a transaction with a given exception
* The transaction promise will be rejected with this exception.
*/
function abortWithException(txn, e) {
function abortWithException(txn: IDBTransaction, e: Error) {
// We cheekily stick our exception onto the transaction object here
// We could alternatively make the thing we pass back to the app
// an object containing the transaction and exception.
txn._mx_abortexception = e;
(txn as IWrappedIDBTransaction)._mx_abortexception = e;
try {
txn.abort();
} catch (e) {
@@ -904,28 +984,28 @@ function abortWithException(txn, e) {
}
}
function promiseifyTxn(txn) {
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T> {
return new Promise((resolve, reject) => {
txn.oncomplete = () => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
}
resolve();
resolve(null);
};
txn.onerror = (event) => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
reject(event.target.error);
reject(txn.error);
}
};
txn.onabort = (event) => {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
} else {
logger.log("Error performing indexeddb txn", event);
reject(event.target.error);
reject(txn.error);
}
};
});

View File

@@ -1,7 +1,5 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,12 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from '../../logger';
import { logger, PrefixedLogger } from '../../logger';
import { LocalStorageCryptoStore } from './localStorage-crypto-store';
import { MemoryCryptoStore } from './memory-crypto-store';
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
import { InvalidCryptoStoreError } from '../../errors';
import * as IndexedDBHelpers from "../../indexeddb-helpers";
import {
CryptoStore,
IDeviceData,
IProblem,
ISession,
ISessionInfo,
IWithheld,
Mode,
OutgoingRoomKeyRequest,
} from "./base";
import { IRoomKeyRequestBody } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../../@types/partials";
import { IEncryptedPayload } from "../aes";
/**
* Internal module. indexeddb storage for e2e.
@@ -35,23 +49,30 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers";
*
* @implements {module:crypto/store/base~CryptoStore}
*/
export class IndexedDBCryptoStore {
export class IndexedDBCryptoStore implements CryptoStore {
public static STORE_ACCOUNT = 'account';
public static STORE_SESSIONS = 'sessions';
public static STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
public static STORE_INBOUND_GROUP_SESSIONS_WITHHELD = 'inbound_group_sessions_withheld';
public static STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS = 'shared_history_inbound_group_sessions';
public static STORE_DEVICE_DATA = 'device_data';
public static STORE_ROOMS = 'rooms';
public static STORE_BACKUP = 'sessions_needing_backup';
public static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
return IndexedDBHelpers.exists(indexedDB, dbName);
}
private backendPromise: Promise<CryptoStore> = null;
private backend: CryptoStore = null;
/**
* Create a new IndexedDBCryptoStore
*
* @param {IDBFactory} indexedDB global indexedDB instance
* @param {string} dbName name of db to connect to
*/
constructor(indexedDB, dbName) {
this._indexedDB = indexedDB;
this._dbName = dbName;
this._backendPromise = null;
this._backend = null;
}
static exists(indexedDB, dbName) {
return IndexedDBHelpers.exists(indexedDB, dbName);
}
constructor(private readonly indexedDB: IDBFactory, private readonly dbName: string) {}
/**
* Ensure the database exists and is up-to-date, or fall back to
@@ -62,25 +83,23 @@ export class IndexedDBCryptoStore {
* @return {Promise} resolves to either an IndexedDBCryptoStoreBackend.Backend,
* or a MemoryCryptoStore
*/
startup() {
if (this._backendPromise) {
return this._backendPromise;
public startup(): Promise<CryptoStore> {
if (this.backendPromise) {
return this.backendPromise;
}
this._backendPromise = new Promise((resolve, reject) => {
if (!this._indexedDB) {
this.backendPromise = new Promise<CryptoStore>((resolve, reject) => {
if (!this.indexedDB) {
reject(new Error('no indexeddb support available'));
return;
}
logger.log(`connecting to indexeddb ${this._dbName}`);
logger.log(`connecting to indexeddb ${this.dbName}`);
const req = this._indexedDB.open(
this._dbName, IndexedDBCryptoStoreBackend.VERSION,
);
const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION);
req.onupgradeneeded = (ev) => {
const db = ev.target.result;
const db = req.result;
const oldVersion = ev.oldVersion;
IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion);
};
@@ -93,13 +112,13 @@ export class IndexedDBCryptoStore {
req.onerror = (ev) => {
logger.log("Error connecting to indexeddb", ev);
reject(ev.target.error);
reject(req.error);
};
req.onsuccess = (r) => {
const db = r.target.result;
req.onsuccess = () => {
const db = req.result;
logger.log(`connected to indexeddb ${this._dbName}`);
logger.log(`connected to indexeddb ${this.dbName}`);
resolve(new IndexedDBCryptoStoreBackend.Backend(db));
};
}).then((backend) => {
@@ -114,9 +133,7 @@ export class IndexedDBCryptoStore {
],
(txn) => {
backend.getEndToEndInboundGroupSession('', '', txn, () => {});
}).then(() => {
return backend;
},
}).then(() => backend,
);
}).catch((e) => {
if (e.name === 'VersionError') {
@@ -126,7 +143,7 @@ export class IndexedDBCryptoStore {
throw new InvalidCryptoStoreError(InvalidCryptoStoreError.TOO_NEW);
}
logger.warn(
`unable to connect to indexeddb ${this._dbName}` +
`unable to connect to indexeddb ${this.dbName}` +
`: falling back to localStorage store: ${e}`,
);
@@ -139,10 +156,11 @@ export class IndexedDBCryptoStore {
return new MemoryCryptoStore();
}
}).then(backend => {
this._backend = backend;
this.backend = backend;
return backend as CryptoStore;
});
return this._backendPromise;
return this.backendPromise;
}
/**
@@ -150,15 +168,15 @@ export class IndexedDBCryptoStore {
*
* @returns {Promise} resolves when the store has been cleared.
*/
deleteAllData() {
return new Promise((resolve, reject) => {
if (!this._indexedDB) {
public deleteAllData(): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (!this.indexedDB) {
reject(new Error('no indexeddb support available'));
return;
}
logger.log(`Removing indexeddb instance: ${this._dbName}`);
const req = this._indexedDB.deleteDatabase(this._dbName);
logger.log(`Removing indexeddb instance: ${this.dbName}`);
const req = this.indexedDB.deleteDatabase(this.dbName);
req.onblocked = () => {
logger.log(
@@ -168,11 +186,11 @@ export class IndexedDBCryptoStore {
req.onerror = (ev) => {
logger.log("Error deleting data from indexeddb", ev);
reject(ev.target.error);
reject(req.error);
};
req.onsuccess = () => {
logger.log(`Removed indexeddb instance: ${this._dbName}`);
logger.log(`Removed indexeddb instance: ${this.dbName}`);
resolve();
};
}).catch((e) => {
@@ -193,8 +211,8 @@ export class IndexedDBCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getOrAddOutgoingRoomKeyRequest(request) {
return this._backend.getOrAddOutgoingRoomKeyRequest(request);
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
return this.backend.getOrAddOutgoingRoomKeyRequest(request);
}
/**
@@ -207,8 +225,8 @@ export class IndexedDBCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
* not found
*/
getOutgoingRoomKeyRequest(requestBody) {
return this._backend.getOutgoingRoomKeyRequest(requestBody);
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.getOutgoingRoomKeyRequest(requestBody);
}
/**
@@ -221,8 +239,8 @@ export class IndexedDBCryptoStore {
* there are no pending requests in those states. If there are multiple
* requests in those states, an arbitrary one is chosen.
*/
getOutgoingRoomKeyRequestByState(wantedStates) {
return this._backend.getOutgoingRoomKeyRequestByState(wantedStates);
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.getOutgoingRoomKeyRequestByState(wantedStates);
}
/**
@@ -232,8 +250,8 @@ export class IndexedDBCryptoStore {
* @param {Number} wantedState
* @return {Promise<Array<*>>} Returns an array of requests in the given state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
return this._backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
}
/**
@@ -246,8 +264,12 @@ export class IndexedDBCryptoStore {
* @return {Promise} resolves to a list of all the
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
*/
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
return this._backend.getOutgoingRoomKeyRequestsByTarget(
public getOutgoingRoomKeyRequestsByTarget(
userId: string,
deviceId: string,
wantedStates: number[],
): Promise<OutgoingRoomKeyRequest[]> {
return this.backend.getOutgoingRoomKeyRequestsByTarget(
userId, deviceId, wantedStates,
);
}
@@ -264,8 +286,12 @@ export class IndexedDBCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
return this._backend.updateOutgoingRoomKeyRequest(
public updateOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
updates: Partial<OutgoingRoomKeyRequest>,
): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.updateOutgoingRoomKeyRequest(
requestId, expectedState, updates,
);
}
@@ -279,8 +305,11 @@ export class IndexedDBCryptoStore {
*
* @returns {Promise} resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
return this._backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
public deleteOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
}
// Olm Account
@@ -292,8 +321,8 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
* @param {function(string)} func Called with the account pickle
*/
getAccount(txn, func) {
this._backend.getAccount(txn, func);
public getAccount(txn: IDBTransaction, func: (accountPickle: string) => void) {
this.backend.getAccount(txn, func);
}
/**
@@ -301,10 +330,10 @@ export class IndexedDBCryptoStore {
* This requires an active transaction. See doTxn().
*
* @param {*} txn An active transaction. See doTxn().
* @param {string} newData The new account pickle to store.
* @param {string} accountPickle The new account pickle to store.
*/
storeAccount(txn, newData) {
this._backend.storeAccount(txn, newData);
public storeAccount(txn: IDBTransaction, accountPickle: string): void {
this.backend.storeAccount(txn, accountPickle);
}
/**
@@ -315,8 +344,8 @@ export class IndexedDBCryptoStore {
* @param {function(string)} func Called with the account keys object:
* { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed
*/
getCrossSigningKeys(txn, func) {
this._backend.getCrossSigningKeys(txn, func);
public getCrossSigningKeys(txn: IDBTransaction, func: (keys: Record<string, ICrossSigningKey>) => void): void {
this.backend.getCrossSigningKeys(txn, func);
}
/**
@@ -324,8 +353,12 @@ export class IndexedDBCryptoStore {
* @param {function(string)} func Called with the private key
* @param {string} type A key type
*/
getSecretStorePrivateKey(txn, func, type) {
this._backend.getSecretStorePrivateKey(txn, func, type);
public getSecretStorePrivateKey<T>(
txn: IDBTransaction,
func: (key: IEncryptedPayload | null) => T,
type: string,
): void {
this.backend.getSecretStorePrivateKey(txn, func, type);
}
/**
@@ -334,8 +367,8 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
* @param {string} keys keys object as getCrossSigningKeys()
*/
storeCrossSigningKeys(txn, keys) {
this._backend.storeCrossSigningKeys(txn, keys);
public storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void {
this.backend.storeCrossSigningKeys(txn, keys);
}
/**
@@ -345,8 +378,8 @@ export class IndexedDBCryptoStore {
* @param {string} type The type of cross-signing private key to store
* @param {string} key keys object as getCrossSigningKeys()
*/
storeSecretStorePrivateKey(txn, type, key) {
this._backend.storeSecretStorePrivateKey(txn, type, key);
public storeSecretStorePrivateKey(txn: IDBTransaction, type: string, key: IEncryptedPayload): void {
this.backend.storeSecretStorePrivateKey(txn, type, key);
}
// Olm sessions
@@ -356,8 +389,8 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
* @param {function(int)} func Called with the count of sessions
*/
countEndToEndSessions(txn, func) {
this._backend.countEndToEndSessions(txn, func);
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
this.backend.countEndToEndSessions(txn, func);
}
/**
@@ -372,8 +405,13 @@ export class IndexedDBCryptoStore {
* timestamp in milliseconds at which the session last received
* a message.
*/
getEndToEndSession(deviceKey, sessionId, txn, func) {
this._backend.getEndToEndSession(deviceKey, sessionId, txn, func);
public getEndToEndSession(
deviceKey: string,
sessionId: string,
txn: IDBTransaction,
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
): void {
this.backend.getEndToEndSession(deviceKey, sessionId, txn, func);
}
/**
@@ -387,8 +425,12 @@ export class IndexedDBCryptoStore {
* timestamp in milliseconds at which the session last received
* a message.
*/
getEndToEndSessions(deviceKey, txn, func) {
this._backend.getEndToEndSessions(deviceKey, txn, func);
public getEndToEndSessions(
deviceKey: string,
txn: IDBTransaction,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void {
this.backend.getEndToEndSessions(deviceKey, txn, func);
}
/**
@@ -398,8 +440,8 @@ export class IndexedDBCryptoStore {
* an object with, deviceKey, lastReceivedMessageTs, sessionId
* and session keys.
*/
getAllEndToEndSessions(txn, func) {
this._backend.getAllEndToEndSessions(txn, func);
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
this.backend.getAllEndToEndSessions(txn, func);
}
/**
@@ -409,22 +451,25 @@ export class IndexedDBCryptoStore {
* @param {string} sessionInfo Session information object
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
this._backend.storeEndToEndSession(
deviceKey, sessionId, sessionInfo, txn,
);
public storeEndToEndSession(
deviceKey: string,
sessionId: string,
sessionInfo: ISessionInfo,
txn: IDBTransaction,
): void {
this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
}
storeEndToEndSessionProblem(deviceKey, type, fixed) {
return this._backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
public storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
}
getEndToEndSessionProblem(deviceKey, timestamp) {
return this._backend.getEndToEndSessionProblem(deviceKey, timestamp);
public getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
return this.backend.getEndToEndSessionProblem(deviceKey, timestamp);
}
filterOutNotifiedErrorDevices(devices) {
return this._backend.filterOutNotifiedErrorDevices(devices);
public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
return this.backend.filterOutNotifiedErrorDevices(devices);
}
// Inbound group sessions
@@ -438,10 +483,13 @@ export class IndexedDBCryptoStore {
* @param {function(object)} func Called with A map from sessionId
* to Base64 end-to-end session.
*/
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
this._backend.getEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, txn, func,
);
public getEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
txn: IDBTransaction,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void {
this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
}
/**
@@ -451,8 +499,11 @@ export class IndexedDBCryptoStore {
* in the store with an object having keys {senderKey, sessionId,
* sessionData}, then once with null to indicate the end of the list.
*/
getAllEndToEndInboundGroupSessions(txn, func) {
this._backend.getAllEndToEndInboundGroupSessions(txn, func);
public getAllEndToEndInboundGroupSessions(
txn: IDBTransaction,
func: (session: ISession | null) => void,
): void {
this.backend.getAllEndToEndInboundGroupSessions(txn, func);
}
/**
@@ -464,10 +515,13 @@ export class IndexedDBCryptoStore {
* @param {object} sessionData The session data structure
* @param {*} txn An active transaction. See doTxn().
*/
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this._backend.addEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, sessionData, txn,
);
public addEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: IDBTransaction,
): void {
this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
/**
@@ -479,18 +533,22 @@ export class IndexedDBCryptoStore {
* @param {object} sessionData The session data structure
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this._backend.storeEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, sessionData, txn,
);
public storeEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: IDBTransaction,
): void {
this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
this._backend.storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
);
public storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key: string,
sessionId: string,
sessionData: IWithheld,
txn: IDBTransaction,
): void {
this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
}
// End-to-end device tracking
@@ -505,8 +563,8 @@ export class IndexedDBCryptoStore {
* @param {Object} deviceData
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndDeviceData(deviceData, txn) {
this._backend.storeEndToEndDeviceData(deviceData, txn);
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void {
this.backend.storeEndToEndDeviceData(deviceData, txn);
}
/**
@@ -516,8 +574,8 @@ export class IndexedDBCryptoStore {
* @param {function(Object)} func Function called with the
* device data
*/
getEndToEndDeviceData(txn, func) {
this._backend.getEndToEndDeviceData(txn, func);
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
this.backend.getEndToEndDeviceData(txn, func);
}
// End to End Rooms
@@ -528,8 +586,8 @@ export class IndexedDBCryptoStore {
* @param {object} roomInfo The end-to-end info for the room.
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndRoom(roomId, roomInfo, txn) {
this._backend.storeEndToEndRoom(roomId, roomInfo, txn);
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void {
this.backend.storeEndToEndRoom(roomId, roomInfo, txn);
}
/**
@@ -537,20 +595,20 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
* @param {function(Object)} func Function called with the end to end encrypted rooms
*/
getEndToEndRooms(txn, func) {
this._backend.getEndToEndRooms(txn, func);
public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void {
this.backend.getEndToEndRooms(txn, func);
}
// session backups
/**
* Get the inbound group sessions that need to be backed up.
* @param {integer} limit The maximum number of sessions to retrieve. 0
* @param {number} limit The maximum number of sessions to retrieve. 0
* for no limit.
* @returns {Promise} resolves to an array of inbound group sessions
*/
getSessionsNeedingBackup(limit) {
return this._backend.getSessionsNeedingBackup(limit);
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
return this.backend.getSessionsNeedingBackup(limit);
}
/**
@@ -558,8 +616,8 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn(). (optional)
* @returns {Promise} resolves to the number of sessions
*/
countSessionsNeedingBackup(txn) {
return this._backend.countSessionsNeedingBackup(txn);
public countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number> {
return this.backend.countSessionsNeedingBackup(txn);
}
/**
@@ -568,8 +626,8 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn(). (optional)
* @returns {Promise} resolves when the sessions are unmarked
*/
unmarkSessionsNeedingBackup(sessions, txn) {
return this._backend.unmarkSessionsNeedingBackup(sessions, txn);
public unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
return this.backend.unmarkSessionsNeedingBackup(sessions, txn);
}
/**
@@ -578,8 +636,8 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn(). (optional)
* @returns {Promise} resolves when the sessions are marked
*/
markSessionsNeedingBackup(sessions, txn) {
return this._backend.markSessionsNeedingBackup(sessions, txn);
public markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
return this.backend.markSessionsNeedingBackup(sessions, txn);
}
/**
@@ -589,10 +647,13 @@ export class IndexedDBCryptoStore {
* @param {string} sessionId The ID of the session
* @param {*} txn An active transaction. See doTxn(). (optional)
*/
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
this._backend.addSharedHistoryInboundGroupSession(
roomId, senderKey, sessionId, txn,
);
public addSharedHistoryInboundGroupSession(
roomId: string,
senderKey: string,
sessionId: string,
txn?: IDBTransaction,
): void {
this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn);
}
/**
@@ -601,8 +662,11 @@ export class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn(). (optional)
* @returns {Promise} Resolves to an array of [senderKey, sessionId]
*/
getSharedHistoryInboundGroupSessions(roomId, txn) {
return this._backend.getSharedHistoryInboundGroupSessions(roomId, txn);
public getSharedHistoryInboundGroupSessions(
roomId: string,
txn?: IDBTransaction,
): Promise<[senderKey: string, sessionId: string][]> {
return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn);
}
/**
@@ -627,18 +691,7 @@ export class IndexedDBCryptoStore {
* reject with that exception. On synchronous backends, the
* exception will propagate to the caller of the getFoo method.
*/
doTxn(mode, stores, func, log) {
return this._backend.doTxn(mode, stores, func, log);
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise<T> {
return this.backend.doTxn(mode, stores, func, log);
}
}
IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD
= 'inbound_group_sessions_withheld';
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS
= 'shared_history_inbound_group_sessions';
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';

View File

@@ -1,6 +1,5 @@
/*
Copyright 2017, 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,6 +16,12 @@ limitations under the License.
import { logger } from '../../logger';
import { MemoryCryptoStore } from './memory-crypto-store';
import { IDeviceData, IProblem, ISession, ISessionInfo, IWithheld, Mode } from "./base";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { ICrossSigningKey } from "../../client";
import { InboundGroupSessionData } from "../../@types/partials";
import { IEncryptedPayload } from "../aes";
/**
* Internal module. Partial localStorage backed storage for e2e.
@@ -38,23 +43,23 @@ const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.w
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup";
function keyEndToEndSessions(deviceKey) {
function keyEndToEndSessions(deviceKey: string): string {
return E2E_PREFIX + "sessions/" + deviceKey;
}
function keyEndToEndSessionProblems(deviceKey) {
function keyEndToEndSessionProblems(deviceKey: string): string {
return E2E_PREFIX + "session.problems/" + deviceKey;
}
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
function keyEndToEndInboundGroupSession(senderKey: string, sessionId: string): string {
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
}
function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) {
function keyEndToEndInboundGroupSessionWithheld(senderKey: string, sessionId: string): string {
return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId;
}
function keyEndToEndRoomsPrefix(roomId) {
function keyEndToEndRoomsPrefix(roomId: string): string {
return KEY_ROOMS_PREFIX + roomId;
}
@@ -62,24 +67,23 @@ function keyEndToEndRoomsPrefix(roomId) {
* @implements {module:crypto/store/base~CryptoStore}
*/
export class LocalStorageCryptoStore extends MemoryCryptoStore {
constructor(webStore) {
super();
this.store = webStore;
}
static exists(webStore) {
const length = webStore.length;
public static exists(store: Storage): boolean {
const length = store.length;
for (let i = 0; i < length; i++) {
if (webStore.key(i).startsWith(E2E_PREFIX)) {
if (store.key(i).startsWith(E2E_PREFIX)) {
return true;
}
}
return false;
}
constructor(private readonly store: Storage) {
super();
}
// Olm Sessions
countEndToEndSessions(txn, func) {
public countEndToEndSessions(txn: unknown, func: (count: number) => void): void {
let count = 0;
for (let i = 0; i < this.store.length; ++i) {
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) ++count;
@@ -87,9 +91,10 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
func(count);
}
_getEndToEndSessions(deviceKey, txn, func) {
// eslint-disable-next-line @typescript-eslint/naming-convention
private _getEndToEndSessions(deviceKey: string): Record<string, ISessionInfo> {
const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey));
const fixedSessions = {};
const fixedSessions: Record<string, ISessionInfo> = {};
// fix up any old sessions to be objects rather than just the base64 pickle
for (const [sid, val] of Object.entries(sessions || {})) {
@@ -105,16 +110,25 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
return fixedSessions;
}
getEndToEndSession(deviceKey, sessionId, txn, func) {
public getEndToEndSession(
deviceKey: string,
sessionId: string,
txn: unknown,
func: (session: ISessionInfo) => void,
): void {
const sessions = this._getEndToEndSessions(deviceKey);
func(sessions[sessionId] || {});
}
getEndToEndSessions(deviceKey, txn, func) {
public getEndToEndSessions(
deviceKey: string,
txn: unknown,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void {
func(this._getEndToEndSessions(deviceKey) || {});
}
getAllEndToEndSessions(txn, func) {
public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void {
for (let i = 0; i < this.store.length; ++i) {
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) {
const deviceKey = this.store.key(i).split('/')[1];
@@ -125,17 +139,15 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
}
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void {
const sessions = this._getEndToEndSessions(deviceKey) || {};
sessions[sessionId] = sessionInfo;
setJsonItem(
this.store, keyEndToEndSessions(deviceKey), sessions,
);
setJsonItem(this.store, keyEndToEndSessions(deviceKey), sessions);
}
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
const key = keyEndToEndSessionProblems(deviceKey);
const problems = getJsonItem(this.store, key) || [];
const problems = getJsonItem<IProblem[]>(this.store, key) || [];
problems.push({ type, fixed, time: Date.now() });
problems.sort((a, b) => {
return a.time - b.time;
@@ -143,9 +155,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
setJsonItem(this.store, key, problems);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
const key = keyEndToEndSessionProblems(deviceKey);
const problems = getJsonItem(this.store, key) || [];
const problems = getJsonItem<IProblem[]>(this.store, key) || [];
if (!problems.length) {
return null;
}
@@ -162,9 +174,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
}
}
async filterOutNotifiedErrorDevices(devices) {
const notifiedErrorDevices =
getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
const notifiedErrorDevices = getJsonItem<string[]>(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
const ret = [];
for (const device of devices) {
@@ -187,7 +198,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
// Inbound Group Sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
public getEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
txn: unknown,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void {
func(
getJsonItem(
this.store,
@@ -200,7 +216,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
);
}
getAllEndToEndInboundGroupSessions(txn, func) {
public getAllEndToEndInboundGroupSessions(txn: unknown, func: (session: ISession | null) => void): void {
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
@@ -219,7 +235,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
func(null);
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
public addEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: unknown,
): void {
const existing = getJsonItem(
this.store,
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
@@ -231,7 +252,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
}
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
public storeEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: unknown,
): void {
setJsonItem(
this.store,
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
@@ -239,9 +265,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
);
}
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
public storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key: string,
sessionId: string,
sessionData: IWithheld,
txn: unknown,
): void {
setJsonItem(
this.store,
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
@@ -249,25 +278,19 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
);
}
getEndToEndDeviceData(txn, func) {
func(getJsonItem(
this.store, KEY_DEVICE_DATA,
));
public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void {
func(getJsonItem(this.store, KEY_DEVICE_DATA));
}
storeEndToEndDeviceData(deviceData, txn) {
setJsonItem(
this.store, KEY_DEVICE_DATA, deviceData,
);
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void {
setJsonItem(this.store, KEY_DEVICE_DATA, deviceData);
}
storeEndToEndRoom(roomId, roomInfo, txn) {
setJsonItem(
this.store, keyEndToEndRoomsPrefix(roomId), roomInfo,
);
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void {
setJsonItem(this.store, keyEndToEndRoomsPrefix(roomId), roomInfo);
}
getEndToEndRooms(txn, func) {
public getEndToEndRooms(txn: unknown, func: (rooms: Record<string, IRoomEncryption>) => void): void {
const result = {};
const prefix = keyEndToEndRoomsPrefix('');
@@ -281,9 +304,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
func(result);
}
getSessionsNeedingBackup(limit) {
const sessionsNeedingBackup
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
const sessionsNeedingBackup = getJsonItem<string[]>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
const sessions = [];
for (const session in sessionsNeedingBackup) {
@@ -309,13 +331,12 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
return Promise.resolve(sessions);
}
countSessionsNeedingBackup() {
const sessionsNeedingBackup
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
public countSessionsNeedingBackup(): Promise<number> {
const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
return Promise.resolve(Object.keys(sessionsNeedingBackup).length);
}
unmarkSessionsNeedingBackup(sessions) {
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
const sessionsNeedingBackup
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
for (const session of sessions) {
@@ -327,7 +348,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
return Promise.resolve();
}
markSessionsNeedingBackup(sessions) {
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
const sessionsNeedingBackup
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
for (const session of sessions) {
@@ -344,52 +365,46 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
*
* @returns {Promise} Promise which resolves when the store has been cleared.
*/
deleteAllData() {
public deleteAllData(): Promise<void> {
this.store.removeItem(KEY_END_TO_END_ACCOUNT);
return Promise.resolve();
}
// Olm account
getAccount(txn, func) {
const account = getJsonItem(this.store, KEY_END_TO_END_ACCOUNT);
func(account);
public getAccount(txn: unknown, func: (accountPickle: string) => void): void {
const accountPickle = getJsonItem<string>(this.store, KEY_END_TO_END_ACCOUNT);
func(accountPickle);
}
storeAccount(txn, newData) {
setJsonItem(
this.store, KEY_END_TO_END_ACCOUNT, newData,
);
public storeAccount(txn: unknown, accountPickle: string): void {
setJsonItem(this.store, KEY_END_TO_END_ACCOUNT, accountPickle);
}
getCrossSigningKeys(txn, func) {
const keys = getJsonItem(this.store, KEY_CROSS_SIGNING_KEYS);
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void {
const keys = getJsonItem<Record<string, ICrossSigningKey>>(this.store, KEY_CROSS_SIGNING_KEYS);
func(keys);
}
getSecretStorePrivateKey(txn, func, type) {
const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`);
public getSecretStorePrivateKey<T>(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T {
const key = getJsonItem<IEncryptedPayload>(this.store, E2E_PREFIX + `ssss_cache.${type}`);
func(key);
}
storeCrossSigningKeys(txn, keys) {
setJsonItem(
this.store, KEY_CROSS_SIGNING_KEYS, keys,
);
public storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void {
setJsonItem(this.store, KEY_CROSS_SIGNING_KEYS, keys);
}
storeSecretStorePrivateKey(txn, type, key) {
setJsonItem(
this.store, E2E_PREFIX + `ssss_cache.${type}`, key,
);
public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void {
setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key);
}
doTxn(mode, stores, func) {
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T): Promise<T> {
return Promise.resolve(func(null));
}
}
function getJsonItem(store, key) {
function getJsonItem<T>(store: Storage, key: string): T | null {
try {
// if the key is absent, store.getItem() returns null, and
// JSON.parse(null) === null, so this returns null.
@@ -401,6 +416,6 @@ function getJsonItem(store, key) {
return null;
}
function setJsonItem(store, key, val) {
function setJsonItem<T>(store: Storage, key: string, val: T): void {
store.setItem(key, JSON.stringify(val));
}

View File

@@ -1,7 +1,5 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,6 +16,22 @@ limitations under the License.
import { logger } from '../../logger';
import * as utils from "../../utils";
import {
CryptoStore,
IDeviceData,
IProblem,
ISession,
ISessionInfo,
IWithheld,
Mode,
OutgoingRoomKeyRequest,
} from "./base";
import { IRoomKeyRequestBody } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../../@types/partials";
import { IEncryptedPayload } from "../aes";
/**
* Internal module. in-memory storage for e2e.
@@ -28,32 +42,22 @@ import * as utils from "../../utils";
/**
* @implements {module:crypto/store/base~CryptoStore}
*/
export class MemoryCryptoStore {
constructor() {
this._outgoingRoomKeyRequests = [];
this._account = null;
this._crossSigningKeys = null;
this._privateKeys = {};
this._backupKeys = {};
export class MemoryCryptoStore implements CryptoStore {
private outgoingRoomKeyRequests: OutgoingRoomKeyRequest[] = [];
private account: string = null;
private crossSigningKeys: Record<string, ICrossSigningKey> = null;
private privateKeys: Record<string, IEncryptedPayload> = {};
// Map of {devicekey -> {sessionId -> session pickle}}
this._sessions = {};
// Map of {devicekey -> array of problems}
this._sessionProblems = {};
// Map of {userId -> deviceId -> true}
this._notifiedErrorDevices = {};
// Map of {senderCurve25519Key+'/'+sessionId -> session data object}
this._inboundGroupSessions = {};
this._inboundGroupSessionsWithheld = {};
private sessions: { [deviceKey: string]: { [sessionId: string]: ISessionInfo } } = {};
private sessionProblems: { [deviceKey: string]: IProblem[] } = {};
private notifiedErrorDevices: { [userId: string]: { [deviceId: string]: boolean } } = {};
private inboundGroupSessions: { [sessionKey: string]: InboundGroupSessionData } = {};
private inboundGroupSessionsWithheld: Record<string, IWithheld> = {};
// Opaque device data object
this._deviceData = null;
// roomId -> Opaque roomInfo object
this._rooms = {};
// Set of {senderCurve25519Key+'/'+sessionId}
this._sessionsNeedingBackup = {};
// roomId -> array of [senderKey, sessionId]
this._sharedHistoryInboundGroupSessions = {};
}
private deviceData: IDeviceData = null;
private rooms: { [roomId: string]: IRoomEncryption } = {};
private sessionsNeedingBackup: { [sessionKey: string]: boolean } = {};
private sharedHistoryInboundGroupSessions: { [roomId: string]: [senderKey: string, sessionId: string][] } = {};
/**
* Ensure the database exists and is up-to-date.
@@ -62,7 +66,7 @@ export class MemoryCryptoStore {
*
* @return {Promise} resolves to the store.
*/
async startup() {
public async startup(): Promise<CryptoStore> {
// No startup work to do for the memory store.
return this;
}
@@ -72,7 +76,7 @@ export class MemoryCryptoStore {
*
* @returns {Promise} Promise which resolves when the store has been cleared.
*/
deleteAllData() {
public deleteAllData(): Promise<void> {
return Promise.resolve();
}
@@ -86,7 +90,7 @@ export class MemoryCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}: either the
* same instance as passed in, or the existing one.
*/
getOrAddOutgoingRoomKeyRequest(request) {
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
const requestBody = request.requestBody;
return utils.promiseTry(() => {
@@ -109,7 +113,7 @@ export class MemoryCryptoStore {
`enqueueing key request for ${requestBody.room_id} / ` +
requestBody.session_id,
);
this._outgoingRoomKeyRequests.push(request);
this.outgoingRoomKeyRequests.push(request);
return request;
});
}
@@ -124,7 +128,7 @@ export class MemoryCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
* not found
*/
getOutgoingRoomKeyRequest(requestBody) {
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
return Promise.resolve(this._getOutgoingRoomKeyRequest(requestBody));
}
@@ -139,8 +143,9 @@ export class MemoryCryptoStore {
* @return {module:crypto/store/base~OutgoingRoomKeyRequest?}
* the matching request, or null if not found
*/
_getOutgoingRoomKeyRequest(requestBody) {
for (const existing of this._outgoingRoomKeyRequests) {
// eslint-disable-next-line @typescript-eslint/naming-convention
private _getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): OutgoingRoomKeyRequest | null {
for (const existing of this.outgoingRoomKeyRequests) {
if (utils.deepCompare(existing.requestBody, requestBody)) {
return existing;
}
@@ -157,8 +162,8 @@ export class MemoryCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}, or null if
* there are no pending requests in those states
*/
getOutgoingRoomKeyRequestByState(wantedStates) {
for (const req of this._outgoingRoomKeyRequests) {
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
for (const req of this.outgoingRoomKeyRequests) {
for (const state of wantedStates) {
if (req.state === state) {
return Promise.resolve(req);
@@ -173,18 +178,22 @@ export class MemoryCryptoStore {
* @param {Number} wantedState
* @return {Promise<Array<*>>} All OutgoingRoomKeyRequests in state
*/
getAllOutgoingRoomKeyRequestsByState(wantedState) {
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
return Promise.resolve(
this._outgoingRoomKeyRequests.filter(
this.outgoingRoomKeyRequests.filter(
(r) => r.state == wantedState,
),
);
}
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
public getOutgoingRoomKeyRequestsByTarget(
userId: string,
deviceId: string,
wantedStates: number[],
): Promise<OutgoingRoomKeyRequest[]> {
const results = [];
for (const req of this._outgoingRoomKeyRequests) {
for (const req of this.outgoingRoomKeyRequests) {
for (const state of wantedStates) {
if (req.state === state && req.recipients.includes({ userId, deviceId })) {
results.push(req);
@@ -206,8 +215,12 @@ export class MemoryCryptoStore {
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
* updated request, or null if no matching row was found
*/
updateOutgoingRoomKeyRequest(requestId, expectedState, updates) {
for (const req of this._outgoingRoomKeyRequests) {
public updateOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
updates: Partial<OutgoingRoomKeyRequest>,
): Promise<OutgoingRoomKeyRequest | null> {
for (const req of this.outgoingRoomKeyRequests) {
if (req.requestId !== requestId) {
continue;
}
@@ -235,9 +248,12 @@ export class MemoryCryptoStore {
*
* @returns {Promise} resolves once the operation is completed
*/
deleteOutgoingRoomKeyRequest(requestId, expectedState) {
for (let i = 0; i < this._outgoingRoomKeyRequests.length; i++) {
const req = this._outgoingRoomKeyRequests[i];
public deleteOutgoingRoomKeyRequest(
requestId: string,
expectedState: number,
): Promise<OutgoingRoomKeyRequest | null> {
for (let i = 0; i < this.outgoingRoomKeyRequests.length; i++) {
const req = this.outgoingRoomKeyRequests[i];
if (req.requestId !== requestId) {
continue;
@@ -251,7 +267,7 @@ export class MemoryCryptoStore {
return Promise.resolve(null);
}
this._outgoingRoomKeyRequests.splice(i, 1);
this.outgoingRoomKeyRequests.splice(i, 1);
return Promise.resolve(req);
}
@@ -260,48 +276,57 @@ export class MemoryCryptoStore {
// Olm Account
getAccount(txn, func) {
func(this._account);
public getAccount(txn: unknown, func: (accountPickle: string) => void) {
func(this.account);
}
storeAccount(txn, newData) {
this._account = newData;
public storeAccount(txn: unknown, accountPickle: string): void {
this.account = accountPickle;
}
getCrossSigningKeys(txn, func) {
func(this._crossSigningKeys);
public getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey>) => void): void {
func(this.crossSigningKeys);
}
getSecretStorePrivateKey(txn, func, type) {
const result = this._privateKeys[type];
public getSecretStorePrivateKey<T>(txn: unknown, func: (key: IEncryptedPayload | null) => T, type: string): T {
const result = this.privateKeys[type];
return func(result || null);
}
storeCrossSigningKeys(txn, keys) {
this._crossSigningKeys = keys;
public storeCrossSigningKeys(txn: unknown, keys: Record<string, ICrossSigningKey>): void {
this.crossSigningKeys = keys;
}
storeSecretStorePrivateKey(txn, type, key) {
this._privateKeys[type] = key;
public storeSecretStorePrivateKey(txn: unknown, type: string, key: IEncryptedPayload): void {
this.privateKeys[type] = key;
}
// Olm Sessions
countEndToEndSessions(txn, func) {
return Object.keys(this._sessions).length;
public countEndToEndSessions(txn: unknown, func: (count: number) => void): void {
return Object.keys(this.sessions).length;
}
getEndToEndSession(deviceKey, sessionId, txn, func) {
const deviceSessions = this._sessions[deviceKey] || {};
public getEndToEndSession(
deviceKey: string,
sessionId: string,
txn: unknown,
func: (session: ISessionInfo) => void,
): void {
const deviceSessions = this.sessions[deviceKey] || {};
func(deviceSessions[sessionId] || null);
}
getEndToEndSessions(deviceKey, txn, func) {
func(this._sessions[deviceKey] || {});
public getEndToEndSessions(
deviceKey: string,
txn: unknown,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void {
func(this.sessions[deviceKey] || {});
}
getAllEndToEndSessions(txn, func) {
Object.entries(this._sessions).forEach(([deviceKey, deviceSessions]) => {
public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void {
Object.entries(this.sessions).forEach(([deviceKey, deviceSessions]) => {
Object.entries(deviceSessions).forEach(([sessionId, session]) => {
func({
...session,
@@ -312,26 +337,25 @@ export class MemoryCryptoStore {
});
}
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
let deviceSessions = this._sessions[deviceKey];
public storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void {
let deviceSessions = this.sessions[deviceKey];
if (deviceSessions === undefined) {
deviceSessions = {};
this._sessions[deviceKey] = deviceSessions;
this.sessions[deviceKey] = deviceSessions;
}
deviceSessions[sessionId] = sessionInfo;
}
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const problems = this._sessionProblems[deviceKey]
= this._sessionProblems[deviceKey] || [];
public async storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
const problems = this.sessionProblems[deviceKey] = this.sessionProblems[deviceKey] || [];
problems.push({ type, fixed, time: Date.now() });
problems.sort((a, b) => {
return a.time - b.time;
});
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
const problems = this._sessionProblems[deviceKey] || [];
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
const problems = this.sessionProblems[deviceKey] || [];
if (!problems.length) {
return null;
}
@@ -348,9 +372,9 @@ export class MemoryCryptoStore {
}
}
async filterOutNotifiedErrorDevices(devices) {
const notifiedErrorDevices = this._notifiedErrorDevices;
const ret = [];
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
const notifiedErrorDevices = this.notifiedErrorDevices;
const ret: IOlmDevice[] = [];
for (const device of devices) {
const { userId, deviceInfo } = device;
@@ -370,16 +394,24 @@ export class MemoryCryptoStore {
// Inbound Group Sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
public getEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
txn: unknown,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void {
const k = senderCurve25519Key+'/'+sessionId;
func(
this._inboundGroupSessions[k] || null,
this._inboundGroupSessionsWithheld[k] || null,
this.inboundGroupSessions[k] || null,
this.inboundGroupSessionsWithheld[k] || null,
);
}
getAllEndToEndInboundGroupSessions(txn, func) {
for (const key of Object.keys(this._inboundGroupSessions)) {
public getAllEndToEndInboundGroupSessions(
txn: unknown,
func: (session: ISession | null) => void,
): void {
for (const key of Object.keys(this.inboundGroupSessions)) {
// we can't use split, as the components we are trying to split out
// might themselves contain '/' characters. We rely on the
// senderKey being a (32-byte) curve25519 key, base64-encoded
@@ -388,58 +420,71 @@ export class MemoryCryptoStore {
func({
senderKey: key.substr(0, 43),
sessionId: key.substr(44),
sessionData: this._inboundGroupSessions[key],
sessionData: this.inboundGroupSessions[key],
});
}
func(null);
}
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
public addEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: unknown,
): void {
const k = senderCurve25519Key+'/'+sessionId;
if (this._inboundGroupSessions[k] === undefined) {
this._inboundGroupSessions[k] = sessionData;
if (this.inboundGroupSessions[k] === undefined) {
this.inboundGroupSessions[k] = sessionData;
}
}
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
public storeEndToEndInboundGroupSession(
senderCurve25519Key: string,
sessionId: string,
sessionData: InboundGroupSessionData,
txn: unknown,
): void {
this.inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
}
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
public storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key: string,
sessionId: string,
sessionData: IWithheld,
txn: unknown,
): void {
const k = senderCurve25519Key+'/'+sessionId;
this._inboundGroupSessionsWithheld[k] = sessionData;
this.inboundGroupSessionsWithheld[k] = sessionData;
}
// Device Data
getEndToEndDeviceData(txn, func) {
func(this._deviceData);
public getEndToEndDeviceData(txn: unknown, func: (deviceData: IDeviceData | null) => void): void {
func(this.deviceData);
}
storeEndToEndDeviceData(deviceData, txn) {
this._deviceData = deviceData;
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: unknown): void {
this.deviceData = deviceData;
}
// E2E rooms
storeEndToEndRoom(roomId, roomInfo, txn) {
this._rooms[roomId] = roomInfo;
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: unknown): void {
this.rooms[roomId] = roomInfo;
}
getEndToEndRooms(txn, func) {
func(this._rooms);
public getEndToEndRooms(txn: unknown, func: (rooms: Record<string, IRoomEncryption>) => void): void {
func(this.rooms);
}
getSessionsNeedingBackup(limit) {
const sessions = [];
for (const session in this._sessionsNeedingBackup) {
if (this._inboundGroupSessions[session]) {
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
const sessions: ISession[] = [];
for (const session in this.sessionsNeedingBackup) {
if (this.inboundGroupSessions[session]) {
sessions.push({
senderKey: session.substr(0, 43),
sessionId: session.substr(44),
sessionData: this._inboundGroupSessions[session],
sessionData: this.inboundGroupSessions[session],
});
if (limit && session.length >= limit) {
break;
@@ -449,39 +494,39 @@ export class MemoryCryptoStore {
return Promise.resolve(sessions);
}
countSessionsNeedingBackup() {
return Promise.resolve(Object.keys(this._sessionsNeedingBackup).length);
public countSessionsNeedingBackup(): Promise<number> {
return Promise.resolve(Object.keys(this.sessionsNeedingBackup).length);
}
unmarkSessionsNeedingBackup(sessions) {
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
for (const session of sessions) {
const sessionKey = session.senderKey + '/' + session.sessionId;
delete this._sessionsNeedingBackup[sessionKey];
delete this.sessionsNeedingBackup[sessionKey];
}
return Promise.resolve();
}
markSessionsNeedingBackup(sessions) {
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
for (const session of sessions) {
const sessionKey = session.senderKey + '/' + session.sessionId;
this._sessionsNeedingBackup[sessionKey] = true;
this.sessionsNeedingBackup[sessionKey] = true;
}
return Promise.resolve();
}
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId) {
const sessions = this._sharedHistoryInboundGroupSessions[roomId] || [];
public addSharedHistoryInboundGroupSession(roomId: string, senderKey: string, sessionId: string): void {
const sessions = this.sharedHistoryInboundGroupSessions[roomId] || [];
sessions.push([senderKey, sessionId]);
this._sharedHistoryInboundGroupSessions[roomId] = sessions;
this.sharedHistoryInboundGroupSessions[roomId] = sessions;
}
getSharedHistoryInboundGroupSessions(roomId) {
return Promise.resolve(this._sharedHistoryInboundGroupSessions[roomId] || []);
public getSharedHistoryInboundGroupSessions(roomId: string): Promise<[senderKey: string, sessionId: string][]> {
return Promise.resolve(this.sharedHistoryInboundGroupSessions[roomId] || []);
}
// Session key backups
doTxn(mode, stores, func) {
public doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn?: unknown) => T): Promise<T> {
return Promise.resolve(func(null));
}
}

View File

@@ -22,8 +22,8 @@ limitations under the License.
* @param {string} dbName The database name to test for
* @returns {boolean} Whether the database exists
*/
export function exists(indexedDB, dbName) {
return new Promise((resolve, reject) => {
export function exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
let exists = true;
const req = indexedDB.open(dbName);
req.onupgradeneeded = () => {
@@ -31,7 +31,7 @@ export function exists(indexedDB, dbName) {
// should only fire if the DB did not exist before at any version.
exists = false;
};
req.onblocked = () => reject();
req.onblocked = () => reject(req.error);
req.onsuccess = () => {
const db = req.result;
db.close();
@@ -45,6 +45,6 @@ export function exists(indexedDB, dbName) {
}
resolve(exists);
};
req.onerror = ev => reject(ev.target.error);
req.onerror = ev => reject(req.error);
});
}

View File

@@ -62,7 +62,7 @@ log.methodFactory = function(methodName, logLevel, loggerName) {
export const logger: PrefixedLogger = log.getLogger(DEFAULT_NAMESPACE);
logger.setLevel(log.levels.DEBUG);
interface PrefixedLogger extends Logger {
export interface PrefixedLogger extends Logger {
withPrefix?: (prefix: string) => PrefixedLogger;
prefix?: string;
}

View File

@@ -117,7 +117,7 @@ function reqAsCursorPromise(req: IDBRequest<IDBCursor | null>): Promise<IDBCurso
}
export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
public static exists(indexedDB: IDBFactory, dbName: string): boolean {
public static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
dbName = "matrix-js-sdk:" + (dbName || "default");
return IndexedDBHelpers.exists(indexedDB, dbName);
}

View File

@@ -47,7 +47,7 @@ interface IOpts extends IBaseOpts {
}
export class IndexedDBStore extends MemoryStore {
static exists(indexedDB: IDBFactory, dbName: string): boolean {
static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
return LocalIndexedDBStoreBackend.exists(indexedDB, dbName);
}