1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-09 10:22:46 +03:00

Remove unused sessionStore (#2455)

* Remove unused sessionStorage layer

* Move pending event abstraction into its temporary home

* Add test coverage

* Tweak

* Fix tests mocks

* Add coverage

* Add coverage
This commit is contained in:
Michael Telatynski
2022-06-14 21:29:21 +01:00
committed by GitHub
parent eb8491c91b
commit b9ca3ceacd
15 changed files with 128 additions and 348 deletions

View File

@@ -23,7 +23,6 @@ import MockHttpBackend from 'matrix-mock-request';
import { LocalStorageCryptoStore } from '../src/crypto/store/localStorage-crypto-store'; import { LocalStorageCryptoStore } from '../src/crypto/store/localStorage-crypto-store';
import { logger } from '../src/logger'; import { logger } from '../src/logger';
import { WebStorageSessionStore } from "../src/store/session/webstorage";
import { syncPromise } from "./test-utils/test-utils"; import { syncPromise } from "./test-utils/test-utils";
import { createClient } from "../src/matrix"; import { createClient } from "../src/matrix";
import { ICreateClientOpts, IDownloadKeyResult, MatrixClient, PendingEventOrdering } from "../src/client"; import { ICreateClientOpts, IDownloadKeyResult, MatrixClient, PendingEventOrdering } from "../src/client";
@@ -53,7 +52,6 @@ export class TestClient {
if (sessionStoreBackend === undefined) { if (sessionStoreBackend === undefined) {
sessionStoreBackend = new MockStorageApi(); sessionStoreBackend = new MockStorageApi();
} }
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
this.httpBackend = new MockHttpBackend(); this.httpBackend = new MockHttpBackend();
@@ -62,7 +60,6 @@ export class TestClient {
userId: userId, userId: userId,
accessToken: accessToken, accessToken: accessToken,
deviceId: deviceId, deviceId: deviceId,
sessionStore: sessionStore,
request: this.httpBackend.requestFn as IHttpOpts["request"], request: this.httpBackend.requestFn as IHttpOpts["request"],
...options, ...options,
}; };

View File

@@ -3,7 +3,6 @@ import '../olm-loader';
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { Crypto } from "../../src/crypto"; import { Crypto } from "../../src/crypto";
import { WebStorageSessionStore } from "../../src/store/session/webstorage";
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store"; import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../MockStorageApi"; import { MockStorageApi } from "../MockStorageApi";
import { TestClient } from "../TestClient"; import { TestClient } from "../TestClient";
@@ -14,6 +13,7 @@ import { sleep } from "../../src/utils";
import { CRYPTO_ENABLED } from "../../src/client"; import { CRYPTO_ENABLED } from "../../src/client";
import { DeviceInfo } from "../../src/crypto/deviceinfo"; import { DeviceInfo } from "../../src/crypto/deviceinfo";
import { logger } from '../../src/logger'; import { logger } from '../../src/logger';
import { MemoryStore } from "../../src";
const Olm = global.Olm; const Olm = global.Olm;
@@ -153,7 +153,7 @@ describe("Crypto", function() {
beforeEach(async function() { beforeEach(async function() {
const mockStorage = new MockStorageApi(); const mockStorage = new MockStorageApi();
const sessionStore = new WebStorageSessionStore(mockStorage); const clientStore = new MemoryStore({ localStorage: mockStorage });
const cryptoStore = new MemoryCryptoStore(mockStorage); const cryptoStore = new MemoryCryptoStore(mockStorage);
cryptoStore.storeEndToEndDeviceData({ cryptoStore.storeEndToEndDeviceData({
@@ -180,10 +180,9 @@ describe("Crypto", function() {
crypto = new Crypto( crypto = new Crypto(
mockBaseApis, mockBaseApis,
sessionStore,
"@alice:home.server", "@alice:home.server",
"FLIBBLE", "FLIBBLE",
sessionStore, clientStore,
cryptoStore, cryptoStore,
mockRoomList, mockRoomList,
); );

View File

@@ -21,7 +21,6 @@ import * as olmlib from "../../../src/crypto/olmlib";
import { MatrixClient } from "../../../src/client"; import { MatrixClient } from "../../../src/client";
import { MatrixEvent } from "../../../src/models/event"; import { MatrixEvent } from "../../../src/models/event";
import * as algorithms from "../../../src/crypto/algorithms"; import * as algorithms from "../../../src/crypto/algorithms";
import { WebStorageSessionStore } from "../../../src/store/session/webstorage";
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store"; import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../../MockStorageApi"; import { MockStorageApi } from "../../MockStorageApi";
import * as testUtils from "../../test-utils/test-utils"; import * as testUtils from "../../test-utils/test-utils";
@@ -118,7 +117,7 @@ function saveCrossSigningKeys(k) {
Object.assign(keys, k); Object.assign(keys, k);
} }
function makeTestClient(sessionStore, cryptoStore) { function makeTestClient(cryptoStore) {
const scheduler = [ const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue", "getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction", "setProcessFunction",
@@ -141,7 +140,6 @@ function makeTestClient(sessionStore, cryptoStore) {
scheduler: scheduler, scheduler: scheduler,
userId: "@alice:bar", userId: "@alice:bar",
deviceId: "device", deviceId: "device",
sessionStore: sessionStore,
cryptoStore: cryptoStore, cryptoStore: cryptoStore,
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys }, cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
}); });
@@ -161,7 +159,6 @@ describe("MegolmBackup", function() {
let mockOlmLib; let mockOlmLib;
let mockCrypto; let mockCrypto;
let mockStorage; let mockStorage;
let sessionStore;
let cryptoStore; let cryptoStore;
let megolmDecryption; let megolmDecryption;
beforeEach(async function() { beforeEach(async function() {
@@ -174,7 +171,6 @@ describe("MegolmBackup", function() {
mockCrypto.backupInfo = CURVE25519_BACKUP_INFO; mockCrypto.backupInfo = CURVE25519_BACKUP_INFO;
mockStorage = new MockStorageApi(); mockStorage = new MockStorageApi();
sessionStore = new WebStorageSessionStore(mockStorage);
cryptoStore = new MemoryCryptoStore(mockStorage); cryptoStore = new MemoryCryptoStore(mockStorage);
olmDevice = new OlmDevice(cryptoStore); olmDevice = new OlmDevice(cryptoStore);
@@ -261,7 +257,7 @@ describe("MegolmBackup", function() {
const ibGroupSession = new Olm.InboundGroupSession(); const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key()); ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore); const client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({ megolmDecryption = new MegolmDecryption({
userId: '@user:id', userId: '@user:id',
@@ -340,7 +336,7 @@ describe("MegolmBackup", function() {
const ibGroupSession = new Olm.InboundGroupSession(); const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key()); ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore); const client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({ megolmDecryption = new MegolmDecryption({
userId: '@user:id', userId: '@user:id',
@@ -423,7 +419,7 @@ describe("MegolmBackup", function() {
const ibGroupSession = new Olm.InboundGroupSession(); const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key()); ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore); const client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({ megolmDecryption = new MegolmDecryption({
userId: '@user:id', userId: '@user:id',
@@ -520,7 +516,6 @@ describe("MegolmBackup", function() {
scheduler: scheduler, scheduler: scheduler,
userId: "@alice:bar", userId: "@alice:bar",
deviceId: "device", deviceId: "device",
sessionStore: sessionStore,
cryptoStore: cryptoStore, cryptoStore: cryptoStore,
}); });
@@ -606,7 +601,7 @@ describe("MegolmBackup", function() {
let client; let client;
beforeEach(function() { beforeEach(function() {
client = makeTestClient(sessionStore, cryptoStore); client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({ megolmDecryption = new MegolmDecryption({
userId: '@user:id', userId: '@user:id',

View File

@@ -1555,6 +1555,8 @@ describe("Room", function() {
return Promise.resolve(); return Promise.resolve();
}, },
getSyncToken: () => "sync_token", getSyncToken: () => "sync_token",
getPendingEvents: jest.fn().mockResolvedValue([]),
setPendingEvents: jest.fn().mockResolvedValue(undefined),
}, },
}; };
} }

View File

@@ -17,13 +17,17 @@ limitations under the License.
import 'fake-indexeddb/auto'; import 'fake-indexeddb/auto';
import 'jest-localstorage-mock'; import 'jest-localstorage-mock';
import { IndexedDBStore, IStateEventWithRoomId } from "../../../src"; import { IndexedDBStore, IStateEventWithRoomId, MemoryStore } from "../../../src";
import { emitPromise } from "../../test-utils/test-utils"; import { emitPromise } from "../../test-utils/test-utils";
import { LocalIndexedDBStoreBackend } from "../../../src/store/indexeddb-local-backend"; import { LocalIndexedDBStoreBackend } from "../../../src/store/indexeddb-local-backend";
describe("IndexedDBStore", () => { describe("IndexedDBStore", () => {
afterEach(() => {
jest.clearAllMocks();
});
const roomId = "!room:id";
it("should degrade to MemoryStore on IDB errors", async () => { it("should degrade to MemoryStore on IDB errors", async () => {
const roomId = "!room:id";
const store = new IndexedDBStore({ const store = new IndexedDBStore({
indexedDB: indexedDB, indexedDB: indexedDB,
dbName: "database", dbName: "database",
@@ -69,4 +73,42 @@ describe("IndexedDBStore", () => {
]); ]);
expect(await store.getOutOfBandMembers(roomId)).toHaveLength(2); expect(await store.getOutOfBandMembers(roomId)).toHaveLength(2);
}); });
it("should use MemoryStore methods for pending events if no localStorage", async () => {
jest.spyOn(MemoryStore.prototype, "setPendingEvents");
jest.spyOn(MemoryStore.prototype, "getPendingEvents");
const store = new IndexedDBStore({
indexedDB: indexedDB,
dbName: "database",
localStorage: undefined,
});
const events = [{ type: "test" }];
await store.setPendingEvents(roomId, events);
expect(MemoryStore.prototype.setPendingEvents).toHaveBeenCalledWith(roomId, events);
await expect(store.getPendingEvents(roomId)).resolves.toEqual(events);
expect(MemoryStore.prototype.getPendingEvents).toHaveBeenCalledWith(roomId);
});
it("should persist pending events to localStorage if available", async () => {
jest.spyOn(MemoryStore.prototype, "setPendingEvents");
jest.spyOn(MemoryStore.prototype, "getPendingEvents");
const store = new IndexedDBStore({
indexedDB: indexedDB,
dbName: "database",
localStorage,
});
await expect(store.getPendingEvents(roomId)).resolves.toEqual([]);
const events = [{ type: "test" }];
await store.setPendingEvents(roomId, events);
expect(MemoryStore.prototype.setPendingEvents).not.toHaveBeenCalled();
await expect(store.getPendingEvents(roomId)).resolves.toEqual(events);
expect(MemoryStore.prototype.getPendingEvents).not.toHaveBeenCalled();
expect(localStorage.getItem("mx_pending_events_" + roomId)).toBe(JSON.stringify(events));
await store.setPendingEvents(roomId, []);
expect(localStorage.getItem("mx_pending_events_" + roomId)).toBeNull();
});
}); });

View File

@@ -168,7 +168,6 @@ import {
import { IAbortablePromise, IdServerUnbindResult, IImageInfo, Preset, Visibility } from "./@types/partials"; import { IAbortablePromise, IdServerUnbindResult, IImageInfo, Preset, Visibility } from "./@types/partials";
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper"; import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper";
import { randomString } from "./randomstring"; import { randomString } from "./randomstring";
import { WebStorageSessionStore } from "./store/session/webstorage";
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup"; import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace"; import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
import { ISignatures } from "./@types/signed"; import { ISignatures } from "./@types/signed";
@@ -195,7 +194,6 @@ import { Thread, THREAD_RELATION_TYPE } from "./models/thread";
import { MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon"; import { MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
export type Store = IStore; export type Store = IStore;
export type SessionStore = WebStorageSessionStore;
export type Callback<T = any> = (err: Error | any | null, data?: T) => void; export type Callback<T = any> = (err: Error | any | null, data?: T) => void;
export type ResetTimelineCallback = (roomId: string) => boolean; export type ResetTimelineCallback = (roomId: string) => boolean;
@@ -315,14 +313,6 @@ export interface ICreateClientOpts {
*/ */
pickleKey?: string; pickleKey?: string;
/**
* A store to be used for end-to-end crypto session data. Most data has been
* migrated out of here to `cryptoStore` instead. If not specified,
* end-to-end crypto will be disabled. The `createClient` helper
* _will not_ create this store at the moment.
*/
sessionStore?: SessionStore;
verificationMethods?: Array<VerificationMethod>; verificationMethods?: Array<VerificationMethod>;
/** /**
@@ -897,7 +887,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public timelineSupport = false; public timelineSupport = false;
public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {}; public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};
public identityServer: IIdentityServerProvider; public identityServer: IIdentityServerProvider;
public sessionStore: SessionStore; // XXX: Intended private, used in code.
public http: MatrixHttpApi; // XXX: Intended private, used in code. public http: MatrixHttpApi; // XXX: Intended private, used in code.
public crypto: Crypto; // XXX: Intended private, used in code. public crypto: Crypto; // XXX: Intended private, used in code.
public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code. public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code.
@@ -1029,7 +1018,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.timelineSupport = Boolean(opts.timelineSupport); this.timelineSupport = Boolean(opts.timelineSupport);
this.cryptoStore = opts.cryptoStore; this.cryptoStore = opts.cryptoStore;
this.sessionStore = opts.sessionStore;
this.verificationMethods = opts.verificationMethods; this.verificationMethods = opts.verificationMethods;
this.cryptoCallbacks = opts.cryptoCallbacks || {}; this.cryptoCallbacks = opts.cryptoCallbacks || {};
@@ -1654,10 +1642,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return; return;
} }
if (!this.sessionStore) {
// this is temporary, the sessionstore is supposed to be going away
throw new Error(`Cannot enable encryption: no sessionStore provided`);
}
if (!this.cryptoStore) { if (!this.cryptoStore) {
// the cryptostore is provided by sdk.createClient, so this shouldn't happen // the cryptostore is provided by sdk.createClient, so this shouldn't happen
throw new Error(`Cannot enable encryption: no cryptoStore provided`); throw new Error(`Cannot enable encryption: no cryptoStore provided`);
@@ -1686,8 +1670,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const crypto = new Crypto( const crypto = new Crypto(
this, this,
this.sessionStore, userId,
userId, this.deviceId, this.deviceId,
this.store, this.store,
this.cryptoStore, this.cryptoStore,
this.roomList, this.roomList,

View File

@@ -75,7 +75,6 @@ import {
ISignedKey, ISignedKey,
IUploadKeySignaturesResponse, IUploadKeySignaturesResponse,
MatrixClient, MatrixClient,
SessionStore,
} from "../client"; } from "../client";
import type { IRoomEncryption, RoomList } from "./RoomList"; import type { IRoomEncryption, RoomList } from "./RoomList";
import { IKeyBackupInfo } from "./keybackup"; import { IKeyBackupInfo } from "./keybackup";
@@ -323,9 +322,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* *
* @param {MatrixClient} baseApis base matrix api interface * @param {MatrixClient} baseApis base matrix api interface
* *
* @param {module:store/session/webstorage~WebStorageSessionStore} sessionStore
* Store to be used for end-to-end crypto session data
*
* @param {string} userId The user ID for the local user * @param {string} userId The user ID for the local user
* *
* @param {string} deviceId The identifier for this device. * @param {string} deviceId The identifier for this device.
@@ -343,7 +339,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*/ */
constructor( constructor(
public readonly baseApis: MatrixClient, public readonly baseApis: MatrixClient,
public readonly sessionStore: SessionStore,
public readonly userId: string, public readonly userId: string,
private readonly deviceId: string, private readonly deviceId: string,
private readonly clientStore: IStore, private readonly clientStore: IStore,
@@ -1725,13 +1720,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
logger.info(`Finished device verification upgrade for ${userId}`); logger.info(`Finished device verification upgrade for ${userId}`);
} }
public async setTrustedBackupPubKey(trustedPubKey: string): Promise<void> {
// This should be redundant post cross-signing is a thing, so just
// plonk it in localStorage for now.
this.sessionStore.setLocalTrustedBackupPubKey(trustedPubKey);
await this.backupManager.checkKeyBackup();
}
/** /**
*/ */
public enableLazyLoading(): void { public enableLazyLoading(): void {

View File

@@ -157,7 +157,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
} }
}).then(backend => { }).then(backend => {
this.backend = backend; this.backend = backend;
return backend as CryptoStore; return backend;
}); });
return this.backendPromise; return this.backendPromise;

View File

@@ -41,7 +41,6 @@ export * from "./interactive-auth";
export * from "./service-types"; export * from "./service-types";
export * from "./store/memory"; export * from "./store/memory";
export * from "./store/indexeddb"; export * from "./store/indexeddb";
export * from "./store/session/webstorage";
export * from "./crypto/store/memory-crypto-store"; export * from "./crypto/store/memory-crypto-store";
export * from "./crypto/store/indexeddb-crypto-store"; export * from "./crypto/store/indexeddb-crypto-store";
export * from "./content-repo"; export * from "./content-repo";

View File

@@ -368,18 +368,16 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) { if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
this.pendingEventList = []; this.pendingEventList = [];
const serializedPendingEventList = client.sessionStore.store.getItem(pendingEventsKey(this.roomId)); this.client.store.getPendingEvents(this.roomId).then(events => {
if (serializedPendingEventList) { events.forEach(async (serializedEvent: Partial<IEvent>) => {
JSON.parse(serializedPendingEventList) const event = new MatrixEvent(serializedEvent);
.forEach(async (serializedEvent: Partial<IEvent>) => { if (event.getType() === EventType.RoomMessageEncrypted) {
const event = new MatrixEvent(serializedEvent); await event.attemptDecryption(this.client.crypto);
if (event.getType() === EventType.RoomMessageEncrypted) { }
await event.attemptDecryption(this.client.crypto); event.setStatus(EventStatus.NOT_SENT);
} this.addPendingEvent(event, event.getTxnId());
event.setStatus(EventStatus.NOT_SENT); });
this.addPendingEvent(event, event.getTxnId()); });
});
}
} }
// awaited by getEncryptionTargetMembers while room members are loading // awaited by getEncryptionTargetMembers while room members are loading
@@ -2075,15 +2073,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
return isEventEncrypted || !isRoomEncrypted; return isEventEncrypted || !isRoomEncrypted;
}); });
const { store } = this.client.sessionStore; this.client.store.setPendingEvents(this.roomId, pendingEvents);
if (this.pendingEventList.length > 0) {
store.setItem(
pendingEventsKey(this.roomId),
JSON.stringify(pendingEvents),
);
} else {
store.removeItem(pendingEventsKey(this.roomId));
}
} }
} }
@@ -3112,14 +3102,6 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
} }
} }
/**
* @param {string} roomId ID of the current room
* @returns {string} Storage key to retrieve pending events
*/
function pendingEventsKey(roomId: string): string {
return `mx_pending_events_${roomId}`;
}
// a map from current event status to a list of allowed next statuses // a map from current event status to a list of allowed next statuses
const ALLOWED_TRANSITIONS: Record<EventStatus, EventStatus[]> = { const ALLOWED_TRANSITIONS: Record<EventStatus, EventStatus[]> = {
[EventStatus.ENCRYPTING]: [ [EventStatus.ENCRYPTING]: [

View File

@@ -17,7 +17,7 @@ limitations under the License.
import { EventType } from "../@types/event"; import { EventType } from "../@types/event";
import { Room } from "../models/room"; import { Room } from "../models/room";
import { User } from "../models/user"; import { User } from "../models/user";
import { MatrixEvent } from "../models/event"; import { IEvent, MatrixEvent } from "../models/event";
import { Filter } from "../filter"; import { Filter } from "../filter";
import { RoomSummary } from "../models/room-summary"; import { RoomSummary } from "../models/room-summary";
import { IMinimalEvent, IRooms, ISyncResponse } from "../sync-accumulator"; import { IMinimalEvent, IRooms, ISyncResponse } from "../sync-accumulator";
@@ -218,4 +218,8 @@ export interface IStore {
getClientOptions(): Promise<IStartClientOpts>; getClientOptions(): Promise<IStartClientOpts>;
storeClientOptions(options: IStartClientOpts): Promise<void>; storeClientOptions(options: IStartClientOpts): Promise<void>;
getPendingEvents(roomId: string): Promise<Partial<IEvent>[]>;
setPendingEvents(roomId: string, events: Partial<IEvent>[]): Promise<void>;
} }

View File

@@ -325,6 +325,40 @@ export class IndexedDBStore extends MemoryStore {
} }
}; };
} }
// XXX: ideally these would be stored in indexeddb as part of the room but,
// we don't store rooms as such and instead accumulate entire sync responses atm.
public async getPendingEvents(roomId: string): Promise<Partial<IEvent>[]> {
if (!this.localStorage) return super.getPendingEvents(roomId);
const serialized = this.localStorage.getItem(pendingEventsKey(roomId));
if (serialized) {
try {
return JSON.parse(serialized);
} catch (e) {
logger.error("Could not parse persisted pending events", e);
}
}
return [];
}
public async setPendingEvents(roomId: string, events: Partial<IEvent>[]): Promise<void> {
if (!this.localStorage) return super.setPendingEvents(roomId, events);
if (events.length > 0) {
this.localStorage.setItem(pendingEventsKey(roomId), JSON.stringify(events));
} else {
this.localStorage.removeItem(pendingEventsKey(roomId));
}
}
}
/**
* @param {string} roomId ID of the current room
* @returns {string} Storage key to retrieve pending events
*/
function pendingEventsKey(roomId: string): string {
return `mx_pending_events_${roomId}`;
} }
type DegradableFn<A extends Array<any>, T> = (...args: A) => Promise<T>; type DegradableFn<A extends Array<any>, T> = (...args: A) => Promise<T>;

View File

@@ -22,7 +22,7 @@ limitations under the License.
import { EventType } from "../@types/event"; import { EventType } from "../@types/event";
import { Room } from "../models/room"; import { Room } from "../models/room";
import { User } from "../models/user"; import { User } from "../models/user";
import { MatrixEvent } from "../models/event"; import { IEvent, MatrixEvent } from "../models/event";
import { RoomState, RoomStateEvent } from "../models/room-state"; import { RoomState, RoomStateEvent } from "../models/room-state";
import { RoomMember } from "../models/room-member"; import { RoomMember } from "../models/room-member";
import { Filter } from "../filter"; import { Filter } from "../filter";
@@ -48,7 +48,7 @@ export interface IOpts {
* Construct a new in-memory data store for the Matrix Client. * Construct a new in-memory data store for the Matrix Client.
* @constructor * @constructor
* @param {Object=} opts Config options * @param {Object=} opts Config options
* @param {LocalStorage} opts.localStorage The local storage instance to persist * @param {Storage} opts.localStorage The local storage instance to persist
* some forms of data such as tokens. Rooms will NOT be stored. * some forms of data such as tokens. Rooms will NOT be stored.
*/ */
export class MemoryStore implements IStore { export class MemoryStore implements IStore {
@@ -60,8 +60,9 @@ export class MemoryStore implements IStore {
// } // }
private filters: Record<string, Record<string, Filter>> = {}; private filters: Record<string, Record<string, Filter>> = {};
public accountData: Record<string, MatrixEvent> = {}; // type : content public accountData: Record<string, MatrixEvent> = {}; // type : content
private readonly localStorage: Storage; protected readonly localStorage: Storage;
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events] private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
private clientOptions = {}; private clientOptions = {};
constructor(opts: IOpts = {}) { constructor(opts: IOpts = {}) {
@@ -420,4 +421,12 @@ export class MemoryStore implements IStore {
this.clientOptions = Object.assign({}, options); this.clientOptions = Object.assign({}, options);
return Promise.resolve(); return Promise.resolve();
} }
public async getPendingEvents(roomId: string): Promise<Partial<IEvent>[]> {
return this.pendingEvents[roomId] ?? [];
}
public async setPendingEvents(roomId: string, events: Partial<IEvent>[]): Promise<void> {
this.pendingEvents[roomId] = events;
}
} }

View File

@@ -1,263 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 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.
*/
/**
* @module store/session/webstorage
*/
import * as utils from "../../utils";
import { logger } from '../../logger';
const DEBUG = false; // set true to enable console logging.
const E2E_PREFIX = "session.e2e.";
/**
* Construct a web storage session store, capable of storing account keys,
* session keys and access tokens.
* @constructor
* @param {WebStorage} webStore A web storage implementation, e.g.
* 'window.localStorage' or 'window.sessionStorage' or a custom implementation.
* @throws if the supplied 'store' does not meet the Storage interface of the
* WebStorage API.
*/
export function WebStorageSessionStore(webStore) {
this.store = webStore;
if (!utils.isFunction(webStore.getItem) ||
!utils.isFunction(webStore.setItem) ||
!utils.isFunction(webStore.removeItem) ||
!utils.isFunction(webStore.key) ||
typeof(webStore.length) !== 'number'
) {
throw new Error(
"Supplied webStore does not meet the WebStorage API interface",
);
}
}
WebStorageSessionStore.prototype = {
/**
* Remove the stored end to end account for the logged-in user.
*/
removeEndToEndAccount: function() {
this.store.removeItem(KEY_END_TO_END_ACCOUNT);
},
/**
* Load the end to end account for the logged-in user.
* Note that the end-to-end account is now stored in the
* crypto store rather than here: this remains here so
* old sessions can be migrated out of the session store.
* @return {?string} Base64 encoded account.
*/
getEndToEndAccount: function() {
return this.store.getItem(KEY_END_TO_END_ACCOUNT);
},
/**
* Retrieves the known devices for all users.
* @return {object} A map from user ID to map of device ID to keys for the device.
*/
getAllEndToEndDevices: function() {
const prefix = keyEndToEndDevicesForUser('');
const devices = {};
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
const userId = key.slice(prefix.length);
if (key.startsWith(prefix)) devices[userId] = getJsonItem(this.store, key);
}
return devices;
},
getEndToEndDeviceTrackingStatus: function() {
return getJsonItem(this.store, KEY_END_TO_END_DEVICE_LIST_TRACKING_STATUS);
},
/**
* Get the sync token corresponding to the device list.
*
* @return {String?} token
*/
getEndToEndDeviceSyncToken: function() {
return getJsonItem(this.store, KEY_END_TO_END_DEVICE_SYNC_TOKEN);
},
/**
* Removes all end to end device data from the store
*/
removeEndToEndDeviceData: function() {
removeByPrefix(this.store, keyEndToEndDevicesForUser(''));
removeByPrefix(this.store, KEY_END_TO_END_DEVICE_LIST_TRACKING_STATUS);
removeByPrefix(this.store, KEY_END_TO_END_DEVICE_SYNC_TOKEN);
},
/**
* Retrieve the end-to-end sessions between the logged-in user and another
* device.
* @param {string} deviceKey The public key of the other device.
* @return {object} A map from sessionId to Base64 end-to-end session.
*/
getEndToEndSessions: function(deviceKey) {
return getJsonItem(this.store, keyEndToEndSessions(deviceKey));
},
/**
* Retrieve all end-to-end sessions between the logged-in user and other
* devices.
* @return {object} A map of {deviceKey -> {sessionId -> session pickle}}
*/
getAllEndToEndSessions: function() {
const deviceKeys = getKeysWithPrefix(this.store, keyEndToEndSessions(''));
const results = {};
for (const k of deviceKeys) {
const unprefixedKey = k.slice(keyEndToEndSessions('').length);
results[unprefixedKey] = getJsonItem(this.store, k);
}
return results;
},
/**
* Remove all end-to-end sessions from the store
* This is used after migrating sessions awat from the sessions store.
*/
removeAllEndToEndSessions: function() {
removeByPrefix(this.store, keyEndToEndSessions(''));
},
/**
* Retrieve a list of all known inbound group sessions
*
* @return {{senderKey: string, sessionId: string}}
*/
getAllEndToEndInboundGroupSessionKeys: function() {
const prefix = E2E_PREFIX + 'inboundgroupsessions/';
const result = [];
for (let i = 0; i < this.store.length; i++) {
const key = this.store.key(i);
if (!key.startsWith(prefix)) {
continue;
}
// 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
// (hence 43 characters long).
result.push({
senderKey: key.slice(prefix.length, prefix.length + 43),
sessionId: key.slice(prefix.length + 44),
});
}
return result;
},
getEndToEndInboundGroupSession: function(senderKey, sessionId) {
const key = keyEndToEndInboundGroupSession(senderKey, sessionId);
return this.store.getItem(key);
},
removeAllEndToEndInboundGroupSessions: function() {
removeByPrefix(this.store, E2E_PREFIX + 'inboundgroupsessions/');
},
/**
* Get the end-to-end state for all rooms
* @return {object} roomId -> object with the end-to-end info for the room.
*/
getAllEndToEndRooms: function() {
const roomKeys = getKeysWithPrefix(this.store, keyEndToEndRoom(''));
const results = {};
for (const k of roomKeys) {
const unprefixedKey = k.slice(keyEndToEndRoom('').length);
results[unprefixedKey] = getJsonItem(this.store, k);
}
return results;
},
removeAllEndToEndRooms: function() {
removeByPrefix(this.store, keyEndToEndRoom(''));
},
setLocalTrustedBackupPubKey: function(pubkey) {
this.store.setItem(KEY_END_TO_END_TRUSTED_BACKUP_PUBKEY, pubkey);
},
// XXX: This store is deprecated really, but added this as a temporary
// thing until cross-signing lands.
getLocalTrustedBackupPubKey: function() {
return this.store.getItem(KEY_END_TO_END_TRUSTED_BACKUP_PUBKEY);
},
};
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
const KEY_END_TO_END_DEVICE_SYNC_TOKEN = E2E_PREFIX + "device_sync_token";
const KEY_END_TO_END_DEVICE_LIST_TRACKING_STATUS = E2E_PREFIX + "device_tracking";
const KEY_END_TO_END_TRUSTED_BACKUP_PUBKEY = E2E_PREFIX + "trusted_backup_pubkey";
function keyEndToEndDevicesForUser(userId) {
return E2E_PREFIX + "devices/" + userId;
}
function keyEndToEndSessions(deviceKey) {
return E2E_PREFIX + "sessions/" + deviceKey;
}
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
return E2E_PREFIX + "inboundgroupsessions/" + senderKey + "/" + sessionId;
}
function keyEndToEndRoom(roomId) {
return E2E_PREFIX + "rooms/" + roomId;
}
function getJsonItem(store, key) {
try {
// if the key is absent, store.getItem() returns null, and
// JSON.parse(null) === null, so this returns null.
return JSON.parse(store.getItem(key));
} catch (e) {
debuglog("Failed to get key %s: %s", key, e);
debuglog(e.stack);
}
return null;
}
function getKeysWithPrefix(store, prefix) {
const results = [];
for (let i = 0; i < store.length; ++i) {
const key = store.key(i);
if (key.startsWith(prefix)) results.push(key);
}
return results;
}
function removeByPrefix(store, prefix) {
const toRemove = [];
for (let i = 0; i < store.length; ++i) {
const key = store.key(i);
if (key.startsWith(prefix)) toRemove.push(key);
}
for (const key of toRemove) {
store.removeItem(key);
}
}
function debuglog(...args) {
if (DEBUG) {
logger.log(...args);
}
}

View File

@@ -22,7 +22,7 @@ limitations under the License.
import { EventType } from "../@types/event"; import { EventType } from "../@types/event";
import { Room } from "../models/room"; import { Room } from "../models/room";
import { User } from "../models/user"; import { User } from "../models/user";
import { MatrixEvent } from "../models/event"; import { IEvent, MatrixEvent } from "../models/event";
import { Filter } from "../filter"; import { Filter } from "../filter";
import { ISavedSync, IStore } from "./index"; import { ISavedSync, IStore } from "./index";
import { RoomSummary } from "../models/room-summary"; import { RoomSummary } from "../models/room-summary";
@@ -262,4 +262,12 @@ export class StubStore implements IStore {
public storeClientOptions(options: object): Promise<void> { public storeClientOptions(options: object): Promise<void> {
return Promise.resolve(); return Promise.resolve();
} }
public async getPendingEvents(roomId: string): Promise<Partial<IEvent>[]> {
return [];
}
public setPendingEvents(roomId: string, events: Partial<IEvent>[]): Promise<void> {
return Promise.resolve();
}
} }