You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-29 16:43:09 +03:00
Merge pull request #597 from matrix-org/dbkr/e2e_rooms_indexeddb
Migrate room encryption store to crypto store
This commit is contained in:
@@ -42,6 +42,7 @@ const MatrixBaseApis = require("./base-apis");
|
|||||||
const MatrixError = httpApi.MatrixError;
|
const MatrixError = httpApi.MatrixError;
|
||||||
|
|
||||||
import ReEmitter from './ReEmitter';
|
import ReEmitter from './ReEmitter';
|
||||||
|
import RoomList from './crypto/RoomList';
|
||||||
|
|
||||||
const SCROLLBACK_DELAY_MS = 3000;
|
const SCROLLBACK_DELAY_MS = 3000;
|
||||||
let CRYPTO_ENABLED = false;
|
let CRYPTO_ENABLED = false;
|
||||||
@@ -181,6 +182,11 @@ function MatrixClient(opts) {
|
|||||||
if (CRYPTO_ENABLED) {
|
if (CRYPTO_ENABLED) {
|
||||||
this.olmVersion = Crypto.getOlmVersion();
|
this.olmVersion = Crypto.getOlmVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of which rooms have encryption enabled: separate from crypto because
|
||||||
|
// we still want to know which rooms are encrypted even if crypto is disabled:
|
||||||
|
// we don't want to start sending unencrypted events to them.
|
||||||
|
this._roomList = new RoomList(this._cryptoStore, this._sessionStore);
|
||||||
}
|
}
|
||||||
utils.inherits(MatrixClient, EventEmitter);
|
utils.inherits(MatrixClient, EventEmitter);
|
||||||
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
|
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
|
||||||
@@ -351,13 +357,6 @@ MatrixClient.prototype.initCrypto = async function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CRYPTO_ENABLED) {
|
|
||||||
throw new Error(
|
|
||||||
`End-to-end encryption not supported in this js-sdk build: did ` +
|
|
||||||
`you remember to load the olm library?`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._sessionStore) {
|
if (!this._sessionStore) {
|
||||||
// this is temporary, the sessionstore is supposed to be going away
|
// this is temporary, the sessionstore is supposed to be going away
|
||||||
throw new Error(`Cannot enable encryption: no sessionStore provided`);
|
throw new Error(`Cannot enable encryption: no sessionStore provided`);
|
||||||
@@ -367,6 +366,16 @@ MatrixClient.prototype.initCrypto = async function() {
|
|||||||
throw new Error(`Cannot enable encryption: no cryptoStore provided`);
|
throw new Error(`Cannot enable encryption: no cryptoStore provided`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialise the list of encrypted rooms (whether or not crypto is enabled)
|
||||||
|
await this._roomList.init();
|
||||||
|
|
||||||
|
if (!CRYPTO_ENABLED) {
|
||||||
|
throw new Error(
|
||||||
|
`End-to-end encryption not supported in this js-sdk build: did ` +
|
||||||
|
`you remember to load the olm library?`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
if (userId === null) {
|
if (userId === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -387,6 +396,7 @@ MatrixClient.prototype.initCrypto = async function() {
|
|||||||
userId, this.deviceId,
|
userId, this.deviceId,
|
||||||
this.store,
|
this.store,
|
||||||
this._cryptoStore,
|
this._cryptoStore,
|
||||||
|
this._roomList,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.reEmitter.reEmit(crypto, [
|
this.reEmitter.reEmit(crypto, [
|
||||||
@@ -646,11 +656,7 @@ MatrixClient.prototype.isRoomEncrypted = function(roomId) {
|
|||||||
// we don't have an m.room.encrypted event, but that might be because
|
// we don't have an m.room.encrypted event, but that might be because
|
||||||
// the server is hiding it from us. Check the store to see if it was
|
// the server is hiding it from us. Check the store to see if it was
|
||||||
// previously encrypted.
|
// previously encrypted.
|
||||||
if (!this._sessionStore) {
|
return this._roomList.isRoomEncrypted(roomId);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(this._sessionStore.getEndToEndRoom(roomId));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
81
src/crypto/RoomList.js
Normal file
81
src/crypto/RoomList.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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 crypto/RoomList
|
||||||
|
*
|
||||||
|
* Manages the list of encrypted rooms
|
||||||
|
*/
|
||||||
|
|
||||||
|
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @alias module:crypto/RoomList
|
||||||
|
*/
|
||||||
|
export default class RoomList {
|
||||||
|
constructor(cryptoStore, sessionStore) {
|
||||||
|
this._cryptoStore = cryptoStore;
|
||||||
|
this._sessionStore = sessionStore;
|
||||||
|
|
||||||
|
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
|
||||||
|
this._roomEncryption = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
let removeSessionStoreRooms = false;
|
||||||
|
await this._cryptoStore.doTxn(
|
||||||
|
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
|
this._cryptoStore.getEndToEndRooms(txn, (result) => {
|
||||||
|
if (result === null || Object.keys(result).length === 0) {
|
||||||
|
// migrate from session store, if there's data there
|
||||||
|
const sessStoreRooms = this._sessionStore.getAllEndToEndRooms();
|
||||||
|
if (sessStoreRooms !== null) {
|
||||||
|
for (const roomId of Object.keys(sessStoreRooms)) {
|
||||||
|
this._cryptoStore.storeEndToEndRoom(
|
||||||
|
roomId, sessStoreRooms[roomId], txn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._roomEncryption = sessStoreRooms;
|
||||||
|
removeSessionStoreRooms = true;
|
||||||
|
} else {
|
||||||
|
this._roomEncryption = result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (removeSessionStoreRooms) {
|
||||||
|
this._sessionStore.removeAllEndToEndRooms();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoomEncryption(roomId) {
|
||||||
|
return this._roomEncryption[roomId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRoomEncrypted(roomId) {
|
||||||
|
return Boolean(this.getRoomEncryption(roomId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRoomEncryption(roomId, roomInfo) {
|
||||||
|
this._roomEncryption[roomId] = roomInfo;
|
||||||
|
await this._cryptoStore.doTxn(
|
||||||
|
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
|
this._cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,15 +59,18 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
|||||||
*
|
*
|
||||||
* @param {module:crypto/store/base~CryptoStore} cryptoStore
|
* @param {module:crypto/store/base~CryptoStore} cryptoStore
|
||||||
* storage for the crypto layer.
|
* storage for the crypto layer.
|
||||||
|
*
|
||||||
|
* @param {RoomList} roomList An initialised RoomList object
|
||||||
*/
|
*/
|
||||||
function Crypto(baseApis, sessionStore, userId, deviceId,
|
function Crypto(baseApis, sessionStore, userId, deviceId,
|
||||||
clientStore, cryptoStore) {
|
clientStore, cryptoStore, roomList) {
|
||||||
this._baseApis = baseApis;
|
this._baseApis = baseApis;
|
||||||
this._sessionStore = sessionStore;
|
this._sessionStore = sessionStore;
|
||||||
this._userId = userId;
|
this._userId = userId;
|
||||||
this._deviceId = deviceId;
|
this._deviceId = deviceId;
|
||||||
this._clientStore = clientStore;
|
this._clientStore = clientStore;
|
||||||
this._cryptoStore = cryptoStore;
|
this._cryptoStore = cryptoStore;
|
||||||
|
this._roomList = roomList;
|
||||||
|
|
||||||
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
this._deviceList = new DeviceList(
|
this._deviceList = new DeviceList(
|
||||||
@@ -587,7 +590,6 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
|||||||
return device;
|
return device;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a room to use encryption (ie, save a flag in the sessionstore).
|
* Configure a room to use encryption (ie, save a flag in the sessionstore).
|
||||||
*
|
*
|
||||||
@@ -601,21 +603,19 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
|||||||
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
||||||
// if we already have encryption in this room, we should ignore this event
|
// if we already have encryption in this room, we should ignore this event
|
||||||
// (for now at least. maybe we should alert the user somehow?)
|
// (for now at least. maybe we should alert the user somehow?)
|
||||||
const existingConfig = this._sessionStore.getEndToEndRoom(roomId);
|
const existingConfig = this._roomList.getRoomEncryption(roomId);
|
||||||
if (existingConfig) {
|
if (existingConfig && JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
||||||
if (JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
|
||||||
console.error("Ignoring m.room.encryption event which requests " +
|
console.error("Ignoring m.room.encryption event which requests " +
|
||||||
"a change of config in " + roomId);
|
"a change of config in " + roomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
const AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
||||||
if (!AlgClass) {
|
if (!AlgClass) {
|
||||||
throw new Error("Unable to encrypt with " + config.algorithm);
|
throw new Error("Unable to encrypt with " + config.algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sessionStore.storeEndToEndRoom(roomId, config);
|
await this._roomList.setRoomEncryption(roomId, config);
|
||||||
|
|
||||||
const alg = new AlgClass({
|
const alg = new AlgClass({
|
||||||
userId: this._userId,
|
userId: this._userId,
|
||||||
@@ -693,16 +693,6 @@ Crypto.prototype.ensureOlmSessionsForUsers = function(users) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether encryption is enabled for a room.
|
|
||||||
* @param {string} roomId the room id to query.
|
|
||||||
* @return {bool} whether encryption is enabled.
|
|
||||||
*/
|
|
||||||
Crypto.prototype.isRoomEncrypted = function(roomId) {
|
|
||||||
return Boolean(this._roomEncryptors[roomId]);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list containing all of the room keys
|
* Get a list containing all of the room keys
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ limitations under the License.
|
|||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
|
|
||||||
export const VERSION = 5;
|
export const VERSION = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a CryptoStore which is backed by an existing
|
* Implementation of a CryptoStore which is backed by an existing
|
||||||
@@ -425,6 +425,30 @@ export class Backend {
|
|||||||
objectStore.put(deviceData, "-");
|
objectStore.put(deviceData, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
const objectStore = txn.objectStore("rooms");
|
||||||
|
objectStore.put(roomInfo, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndRooms(txn, func) {
|
||||||
|
const rooms = {};
|
||||||
|
const objectStore = txn.objectStore("rooms");
|
||||||
|
const getReq = objectStore.openCursor();
|
||||||
|
getReq.onsuccess = function() {
|
||||||
|
const cursor = getReq.result;
|
||||||
|
if (cursor) {
|
||||||
|
rooms[cursor.key] = cursor.value;
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
func(rooms);
|
||||||
|
} catch (e) {
|
||||||
|
abortWithException(txn, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
const txn = this._db.transaction(stores, mode);
|
const txn = this._db.transaction(stores, mode);
|
||||||
const promise = promiseifyTxn(txn);
|
const promise = promiseifyTxn(txn);
|
||||||
@@ -460,6 +484,9 @@ export function upgradeDatabase(db, oldVersion) {
|
|||||||
if (oldVersion < 5) {
|
if (oldVersion < 5) {
|
||||||
db.createObjectStore("device_data");
|
db.createObjectStore("device_data");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
db.createObjectStore("rooms");
|
||||||
|
}
|
||||||
// Expand as needed.
|
// Expand as needed.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -386,6 +386,27 @@ export default class IndexedDBCryptoStore {
|
|||||||
this._backendPromise.value().getEndToEndDeviceData(txn, func);
|
this._backendPromise.value().getEndToEndDeviceData(txn, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End to End Rooms
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the end-to-end state for a room.
|
||||||
|
* @param {string} roomId The room's ID.
|
||||||
|
* @param {object} roomInfo The end-to-end info for the room.
|
||||||
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
*/
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
this._backendPromise.value().storeEndToEndRoom(roomId, roomInfo, txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an object of roomId->roomInfo for all e2e rooms in the store
|
||||||
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
* @param {function(Object)} func Function called with the end to end encrypted rooms
|
||||||
|
*/
|
||||||
|
getEndToEndRooms(txn, func) {
|
||||||
|
this._backendPromise.value().getEndToEndRooms(txn, func);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a transaction on the crypto store. Any store methods
|
* Perform a transaction on the crypto store. Any store methods
|
||||||
* that require a transaction (txn) object to be passed in may
|
* that require a transaction (txn) object to be passed in may
|
||||||
@@ -418,3 +439,4 @@ IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
|
|||||||
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
||||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
||||||
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
||||||
|
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const E2E_PREFIX = "crypto.";
|
|||||||
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
||||||
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
||||||
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
||||||
|
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
|
||||||
|
|
||||||
function keyEndToEndSessions(deviceKey) {
|
function keyEndToEndSessions(deviceKey) {
|
||||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||||
@@ -40,6 +41,10 @@ function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
|||||||
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyEndToEndRoomsPrefix(roomId) {
|
||||||
|
return KEY_ROOMS_PREFIX + roomId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements {module:crypto/store/base~CryptoStore}
|
* @implements {module:crypto/store/base~CryptoStore}
|
||||||
*/
|
*/
|
||||||
@@ -140,6 +145,26 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
setJsonItem(
|
||||||
|
this.store, keyEndToEndRoomsPrefix(roomId), roomInfo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndRooms(txn, func) {
|
||||||
|
const result = {};
|
||||||
|
const prefix = keyEndToEndRoomsPrefix('');
|
||||||
|
|
||||||
|
for (let i = 0; i < this.store.length; ++i) {
|
||||||
|
const key = this.store.key(i);
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
const roomId = key.substr(prefix.length);
|
||||||
|
result[roomId] = getJsonItem(this.store, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func(result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all data from this store.
|
* Delete all data from this store.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export default class MemoryCryptoStore {
|
|||||||
this._inboundGroupSessions = {};
|
this._inboundGroupSessions = {};
|
||||||
// Opaque device data object
|
// Opaque device data object
|
||||||
this._deviceData = null;
|
this._deviceData = null;
|
||||||
|
// roomId -> Opaque roomInfo object
|
||||||
|
this._rooms = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,6 +285,15 @@ export default class MemoryCryptoStore {
|
|||||||
this._deviceData = deviceData;
|
this._deviceData = deviceData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// E2E rooms
|
||||||
|
|
||||||
|
storeEndToEndRoom(roomId, roomInfo, txn) {
|
||||||
|
this._rooms[roomId] = roomInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndRooms(txn, func) {
|
||||||
|
func(this._rooms);
|
||||||
|
}
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
return Promise.resolve(func(null));
|
return Promise.resolve(func(null));
|
||||||
|
|||||||
@@ -174,21 +174,21 @@ WebStorageSessionStore.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the end-to-end state for a room.
|
* Get the end-to-end state for all rooms
|
||||||
* @param {string} roomId The room's ID.
|
* @return {object} roomId -> object with the end-to-end info for the room.
|
||||||
* @param {object} roomInfo The end-to-end info for the room.
|
|
||||||
*/
|
*/
|
||||||
storeEndToEndRoom: function(roomId, roomInfo) {
|
getAllEndToEndRooms: function() {
|
||||||
setJsonItem(this.store, keyEndToEndRoom(roomId), roomInfo);
|
const roomKeys = getKeysWithPrefix(this.store, keyEndToEndRoom(''));
|
||||||
|
const results = {};
|
||||||
|
for (const k of roomKeys) {
|
||||||
|
const unprefixedKey = k.substr(keyEndToEndRoom('').length);
|
||||||
|
results[unprefixedKey] = getJsonItem(this.store, k);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
removeAllEndToEndRooms: function() {
|
||||||
* Get the end-to-end state for a room
|
removeByPrefix(this.store, keyEndToEndRoom(''));
|
||||||
* @param {string} roomId The room's ID.
|
|
||||||
* @return {object} The end-to-end info for the room.
|
|
||||||
*/
|
|
||||||
getEndToEndRoom: function(roomId) {
|
|
||||||
return getJsonItem(this.store, keyEndToEndRoom(roomId));
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -224,10 +224,6 @@ function getJsonItem(store, key) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setJsonItem(store, key, val) {
|
|
||||||
store.setItem(key, JSON.stringify(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getKeysWithPrefix(store, prefix) {
|
function getKeysWithPrefix(store, prefix) {
|
||||||
const results = [];
|
const results = [];
|
||||||
for (let i = 0; i < store.length; ++i) {
|
for (let i = 0; i < store.length; ++i) {
|
||||||
|
|||||||
Reference in New Issue
Block a user