You've already forked matrix-js-sdk
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:
committed by
GitHub
parent
eb8491c91b
commit
b9ca3ceacd
@@ -23,7 +23,6 @@ import MockHttpBackend from 'matrix-mock-request';
|
||||
|
||||
import { LocalStorageCryptoStore } from '../src/crypto/store/localStorage-crypto-store';
|
||||
import { logger } from '../src/logger';
|
||||
import { WebStorageSessionStore } from "../src/store/session/webstorage";
|
||||
import { syncPromise } from "./test-utils/test-utils";
|
||||
import { createClient } from "../src/matrix";
|
||||
import { ICreateClientOpts, IDownloadKeyResult, MatrixClient, PendingEventOrdering } from "../src/client";
|
||||
@@ -53,7 +52,6 @@ export class TestClient {
|
||||
if (sessionStoreBackend === undefined) {
|
||||
sessionStoreBackend = new MockStorageApi();
|
||||
}
|
||||
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
|
||||
|
||||
this.httpBackend = new MockHttpBackend();
|
||||
|
||||
@@ -62,7 +60,6 @@ export class TestClient {
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
deviceId: deviceId,
|
||||
sessionStore: sessionStore,
|
||||
request: this.httpBackend.requestFn as IHttpOpts["request"],
|
||||
...options,
|
||||
};
|
||||
|
@@ -3,7 +3,6 @@ import '../olm-loader';
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { Crypto } from "../../src/crypto";
|
||||
import { WebStorageSessionStore } from "../../src/store/session/webstorage";
|
||||
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
|
||||
import { MockStorageApi } from "../MockStorageApi";
|
||||
import { TestClient } from "../TestClient";
|
||||
@@ -14,6 +13,7 @@ import { sleep } from "../../src/utils";
|
||||
import { CRYPTO_ENABLED } from "../../src/client";
|
||||
import { DeviceInfo } from "../../src/crypto/deviceinfo";
|
||||
import { logger } from '../../src/logger';
|
||||
import { MemoryStore } from "../../src";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@@ -153,7 +153,7 @@ describe("Crypto", function() {
|
||||
|
||||
beforeEach(async function() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
const clientStore = new MemoryStore({ localStorage: mockStorage });
|
||||
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
cryptoStore.storeEndToEndDeviceData({
|
||||
@@ -180,10 +180,9 @@ describe("Crypto", function() {
|
||||
|
||||
crypto = new Crypto(
|
||||
mockBaseApis,
|
||||
sessionStore,
|
||||
"@alice:home.server",
|
||||
"FLIBBLE",
|
||||
sessionStore,
|
||||
clientStore,
|
||||
cryptoStore,
|
||||
mockRoomList,
|
||||
);
|
||||
|
@@ -21,7 +21,6 @@ import * as olmlib from "../../../src/crypto/olmlib";
|
||||
import { MatrixClient } from "../../../src/client";
|
||||
import { MatrixEvent } from "../../../src/models/event";
|
||||
import * as algorithms from "../../../src/crypto/algorithms";
|
||||
import { WebStorageSessionStore } from "../../../src/store/session/webstorage";
|
||||
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
|
||||
import { MockStorageApi } from "../../MockStorageApi";
|
||||
import * as testUtils from "../../test-utils/test-utils";
|
||||
@@ -118,7 +117,7 @@ function saveCrossSigningKeys(k) {
|
||||
Object.assign(keys, k);
|
||||
}
|
||||
|
||||
function makeTestClient(sessionStore, cryptoStore) {
|
||||
function makeTestClient(cryptoStore) {
|
||||
const scheduler = [
|
||||
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
||||
"setProcessFunction",
|
||||
@@ -141,7 +140,6 @@ function makeTestClient(sessionStore, cryptoStore) {
|
||||
scheduler: scheduler,
|
||||
userId: "@alice:bar",
|
||||
deviceId: "device",
|
||||
sessionStore: sessionStore,
|
||||
cryptoStore: cryptoStore,
|
||||
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
|
||||
});
|
||||
@@ -161,7 +159,6 @@ describe("MegolmBackup", function() {
|
||||
let mockOlmLib;
|
||||
let mockCrypto;
|
||||
let mockStorage;
|
||||
let sessionStore;
|
||||
let cryptoStore;
|
||||
let megolmDecryption;
|
||||
beforeEach(async function() {
|
||||
@@ -174,7 +171,6 @@ describe("MegolmBackup", function() {
|
||||
mockCrypto.backupInfo = CURVE25519_BACKUP_INFO;
|
||||
|
||||
mockStorage = new MockStorageApi();
|
||||
sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
olmDevice = new OlmDevice(cryptoStore);
|
||||
@@ -261,7 +257,7 @@ describe("MegolmBackup", function() {
|
||||
const ibGroupSession = new Olm.InboundGroupSession();
|
||||
ibGroupSession.create(groupSession.session_key());
|
||||
|
||||
const client = makeTestClient(sessionStore, cryptoStore);
|
||||
const client = makeTestClient(cryptoStore);
|
||||
|
||||
megolmDecryption = new MegolmDecryption({
|
||||
userId: '@user:id',
|
||||
@@ -340,7 +336,7 @@ describe("MegolmBackup", function() {
|
||||
const ibGroupSession = new Olm.InboundGroupSession();
|
||||
ibGroupSession.create(groupSession.session_key());
|
||||
|
||||
const client = makeTestClient(sessionStore, cryptoStore);
|
||||
const client = makeTestClient(cryptoStore);
|
||||
|
||||
megolmDecryption = new MegolmDecryption({
|
||||
userId: '@user:id',
|
||||
@@ -423,7 +419,7 @@ describe("MegolmBackup", function() {
|
||||
const ibGroupSession = new Olm.InboundGroupSession();
|
||||
ibGroupSession.create(groupSession.session_key());
|
||||
|
||||
const client = makeTestClient(sessionStore, cryptoStore);
|
||||
const client = makeTestClient(cryptoStore);
|
||||
|
||||
megolmDecryption = new MegolmDecryption({
|
||||
userId: '@user:id',
|
||||
@@ -520,7 +516,6 @@ describe("MegolmBackup", function() {
|
||||
scheduler: scheduler,
|
||||
userId: "@alice:bar",
|
||||
deviceId: "device",
|
||||
sessionStore: sessionStore,
|
||||
cryptoStore: cryptoStore,
|
||||
});
|
||||
|
||||
@@ -606,7 +601,7 @@ describe("MegolmBackup", function() {
|
||||
let client;
|
||||
|
||||
beforeEach(function() {
|
||||
client = makeTestClient(sessionStore, cryptoStore);
|
||||
client = makeTestClient(cryptoStore);
|
||||
|
||||
megolmDecryption = new MegolmDecryption({
|
||||
userId: '@user:id',
|
||||
|
@@ -1555,6 +1555,8 @@ describe("Room", function() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getSyncToken: () => "sync_token",
|
||||
getPendingEvents: jest.fn().mockResolvedValue([]),
|
||||
setPendingEvents: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -17,13 +17,17 @@ limitations under the License.
|
||||
import 'fake-indexeddb/auto';
|
||||
import 'jest-localstorage-mock';
|
||||
|
||||
import { IndexedDBStore, IStateEventWithRoomId } from "../../../src";
|
||||
import { IndexedDBStore, IStateEventWithRoomId, MemoryStore } from "../../../src";
|
||||
import { emitPromise } from "../../test-utils/test-utils";
|
||||
import { LocalIndexedDBStoreBackend } from "../../../src/store/indexeddb-local-backend";
|
||||
|
||||
describe("IndexedDBStore", () => {
|
||||
it("should degrade to MemoryStore on IDB errors", async () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const roomId = "!room:id";
|
||||
it("should degrade to MemoryStore on IDB errors", async () => {
|
||||
const store = new IndexedDBStore({
|
||||
indexedDB: indexedDB,
|
||||
dbName: "database",
|
||||
@@ -69,4 +73,42 @@ describe("IndexedDBStore", () => {
|
||||
]);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
@@ -168,7 +168,6 @@ import {
|
||||
import { IAbortablePromise, IdServerUnbindResult, IImageInfo, Preset, Visibility } from "./@types/partials";
|
||||
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper";
|
||||
import { randomString } from "./randomstring";
|
||||
import { WebStorageSessionStore } from "./store/session/webstorage";
|
||||
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
|
||||
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
|
||||
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";
|
||||
|
||||
export type Store = IStore;
|
||||
export type SessionStore = WebStorageSessionStore;
|
||||
|
||||
export type Callback<T = any> = (err: Error | any | null, data?: T) => void;
|
||||
export type ResetTimelineCallback = (roomId: string) => boolean;
|
||||
@@ -315,14 +313,6 @@ export interface ICreateClientOpts {
|
||||
*/
|
||||
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>;
|
||||
|
||||
/**
|
||||
@@ -897,7 +887,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public timelineSupport = false;
|
||||
public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};
|
||||
public identityServer: IIdentityServerProvider;
|
||||
public sessionStore: SessionStore; // 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 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.cryptoStore = opts.cryptoStore;
|
||||
this.sessionStore = opts.sessionStore;
|
||||
this.verificationMethods = opts.verificationMethods;
|
||||
this.cryptoCallbacks = opts.cryptoCallbacks || {};
|
||||
|
||||
@@ -1654,10 +1642,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
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) {
|
||||
// the cryptostore is provided by sdk.createClient, so this shouldn't happen
|
||||
throw new Error(`Cannot enable encryption: no cryptoStore provided`);
|
||||
@@ -1686,8 +1670,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
const crypto = new Crypto(
|
||||
this,
|
||||
this.sessionStore,
|
||||
userId, this.deviceId,
|
||||
userId,
|
||||
this.deviceId,
|
||||
this.store,
|
||||
this.cryptoStore,
|
||||
this.roomList,
|
||||
|
@@ -75,7 +75,6 @@ import {
|
||||
ISignedKey,
|
||||
IUploadKeySignaturesResponse,
|
||||
MatrixClient,
|
||||
SessionStore,
|
||||
} from "../client";
|
||||
import type { IRoomEncryption, RoomList } from "./RoomList";
|
||||
import { IKeyBackupInfo } from "./keybackup";
|
||||
@@ -323,9 +322,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @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} deviceId The identifier for this device.
|
||||
@@ -343,7 +339,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*/
|
||||
constructor(
|
||||
public readonly baseApis: MatrixClient,
|
||||
public readonly sessionStore: SessionStore,
|
||||
public readonly userId: string,
|
||||
private readonly deviceId: string,
|
||||
private readonly clientStore: IStore,
|
||||
@@ -1725,13 +1720,6 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
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 {
|
||||
|
@@ -157,7 +157,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
}
|
||||
}).then(backend => {
|
||||
this.backend = backend;
|
||||
return backend as CryptoStore;
|
||||
return backend;
|
||||
});
|
||||
|
||||
return this.backendPromise;
|
||||
|
@@ -41,7 +41,6 @@ export * from "./interactive-auth";
|
||||
export * from "./service-types";
|
||||
export * from "./store/memory";
|
||||
export * from "./store/indexeddb";
|
||||
export * from "./store/session/webstorage";
|
||||
export * from "./crypto/store/memory-crypto-store";
|
||||
export * from "./crypto/store/indexeddb-crypto-store";
|
||||
export * from "./content-repo";
|
||||
|
@@ -368,10 +368,8 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
|
||||
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
|
||||
this.pendingEventList = [];
|
||||
const serializedPendingEventList = client.sessionStore.store.getItem(pendingEventsKey(this.roomId));
|
||||
if (serializedPendingEventList) {
|
||||
JSON.parse(serializedPendingEventList)
|
||||
.forEach(async (serializedEvent: Partial<IEvent>) => {
|
||||
this.client.store.getPendingEvents(this.roomId).then(events => {
|
||||
events.forEach(async (serializedEvent: Partial<IEvent>) => {
|
||||
const event = new MatrixEvent(serializedEvent);
|
||||
if (event.getType() === EventType.RoomMessageEncrypted) {
|
||||
await event.attemptDecryption(this.client.crypto);
|
||||
@@ -379,7 +377,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
event.setStatus(EventStatus.NOT_SENT);
|
||||
this.addPendingEvent(event, event.getTxnId());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// awaited by getEncryptionTargetMembers while room members are loading
|
||||
@@ -2075,15 +2073,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
return isEventEncrypted || !isRoomEncrypted;
|
||||
});
|
||||
|
||||
const { store } = this.client.sessionStore;
|
||||
if (this.pendingEventList.length > 0) {
|
||||
store.setItem(
|
||||
pendingEventsKey(this.roomId),
|
||||
JSON.stringify(pendingEvents),
|
||||
);
|
||||
} else {
|
||||
store.removeItem(pendingEventsKey(this.roomId));
|
||||
}
|
||||
this.client.store.setPendingEvents(this.roomId, pendingEvents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
const ALLOWED_TRANSITIONS: Record<EventStatus, EventStatus[]> = {
|
||||
[EventStatus.ENCRYPTING]: [
|
||||
|
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
import { EventType } from "../@types/event";
|
||||
import { Room } from "../models/room";
|
||||
import { User } from "../models/user";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { IEvent, MatrixEvent } from "../models/event";
|
||||
import { Filter } from "../filter";
|
||||
import { RoomSummary } from "../models/room-summary";
|
||||
import { IMinimalEvent, IRooms, ISyncResponse } from "../sync-accumulator";
|
||||
@@ -218,4 +218,8 @@ export interface IStore {
|
||||
getClientOptions(): Promise<IStartClientOpts>;
|
||||
|
||||
storeClientOptions(options: IStartClientOpts): Promise<void>;
|
||||
|
||||
getPendingEvents(roomId: string): Promise<Partial<IEvent>[]>;
|
||||
|
||||
setPendingEvents(roomId: string, events: Partial<IEvent>[]): Promise<void>;
|
||||
}
|
||||
|
@@ -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>;
|
||||
|
@@ -22,7 +22,7 @@ limitations under the License.
|
||||
import { EventType } from "../@types/event";
|
||||
import { Room } from "../models/room";
|
||||
import { User } from "../models/user";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { IEvent, MatrixEvent } from "../models/event";
|
||||
import { RoomState, RoomStateEvent } from "../models/room-state";
|
||||
import { RoomMember } from "../models/room-member";
|
||||
import { Filter } from "../filter";
|
||||
@@ -48,7 +48,7 @@ export interface IOpts {
|
||||
* Construct a new in-memory data store for the Matrix Client.
|
||||
* @constructor
|
||||
* @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.
|
||||
*/
|
||||
export class MemoryStore implements IStore {
|
||||
@@ -60,8 +60,9 @@ export class MemoryStore implements IStore {
|
||||
// }
|
||||
private filters: Record<string, Record<string, Filter>> = {};
|
||||
public accountData: Record<string, MatrixEvent> = {}; // type : content
|
||||
private readonly localStorage: Storage;
|
||||
protected readonly localStorage: Storage;
|
||||
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
|
||||
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
|
||||
private clientOptions = {};
|
||||
|
||||
constructor(opts: IOpts = {}) {
|
||||
@@ -420,4 +421,12 @@ export class MemoryStore implements IStore {
|
||||
this.clientOptions = Object.assign({}, options);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ limitations under the License.
|
||||
import { EventType } from "../@types/event";
|
||||
import { Room } from "../models/room";
|
||||
import { User } from "../models/user";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { IEvent, MatrixEvent } from "../models/event";
|
||||
import { Filter } from "../filter";
|
||||
import { ISavedSync, IStore } from "./index";
|
||||
import { RoomSummary } from "../models/room-summary";
|
||||
@@ -262,4 +262,12 @@ export class StubStore implements IStore {
|
||||
public storeClientOptions(options: object): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public async getPendingEvents(roomId: string): Promise<Partial<IEvent>[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
public setPendingEvents(roomId: string, events: Partial<IEvent>[]): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user