Merge pull request #474 from vector-im/snowpack-ts-storage-3

Snowpack + Typescript conversion (Part 3)
This commit is contained in:
Bruno Windels 2021-09-06 12:55:46 +02:00 committed by GitHub
commit 360b4db17a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 274 additions and 134 deletions

View file

@ -22,18 +22,18 @@ import {RoomSummaryStore} from "./stores/RoomSummaryStore";
import {InviteStore} from "./stores/InviteStore"; import {InviteStore} from "./stores/InviteStore";
import {TimelineEventStore} from "./stores/TimelineEventStore"; import {TimelineEventStore} from "./stores/TimelineEventStore";
import {TimelineRelationStore} from "./stores/TimelineRelationStore"; import {TimelineRelationStore} from "./stores/TimelineRelationStore";
import {RoomStateStore} from "./stores/RoomStateStore.js"; import {RoomStateStore} from "./stores/RoomStateStore";
import {RoomMemberStore} from "./stores/RoomMemberStore"; import {RoomMemberStore} from "./stores/RoomMemberStore";
import {TimelineFragmentStore} from "./stores/TimelineFragmentStore.js"; import {TimelineFragmentStore} from "./stores/TimelineFragmentStore";
import {PendingEventStore} from "./stores/PendingEventStore.js"; import {PendingEventStore} from "./stores/PendingEventStore";
import {UserIdentityStore} from "./stores/UserIdentityStore.js"; import {UserIdentityStore} from "./stores/UserIdentityStore";
import {DeviceIdentityStore} from "./stores/DeviceIdentityStore.js"; import {DeviceIdentityStore} from "./stores/DeviceIdentityStore";
import {OlmSessionStore} from "./stores/OlmSessionStore.js"; import {OlmSessionStore} from "./stores/OlmSessionStore";
import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore.js"; import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore";
import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore.js"; import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore";
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore.js"; import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore";
import {OperationStore} from "./stores/OperationStore.js"; import {OperationStore} from "./stores/OperationStore";
import {AccountDataStore} from "./stores/AccountDataStore.js"; import {AccountDataStore} from "./stores/AccountDataStore";
export class Transaction { export class Transaction {
constructor(txn, allowedStoreNames, IDBKeyRange) { constructor(txn, allowedStoreNames, IDBKeyRange) {

View file

@ -3,7 +3,7 @@ import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../room/members/Ro
import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js"; import {addRoomToIdentity} from "../../e2ee/DeviceTracker.js";
import {RoomMemberStore} from "./stores/RoomMemberStore"; import {RoomMemberStore} from "./stores/RoomMemberStore";
import {SessionStore} from "./stores/SessionStore"; import {SessionStore} from "./stores/SessionStore";
import {encodeScopeTypeKey} from "./stores/OperationStore.js"; import {encodeScopeTypeKey} from "./stores/OperationStore";
import {MAX_UNICODE} from "./stores/common"; import {MAX_UNICODE} from "./stores/common";
// FUNCTIONS SHOULD ONLY BE APPENDED!! // FUNCTIONS SHOULD ONLY BE APPENDED!!

View file

@ -13,17 +13,26 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Store} from "../Store";
import {Content} from "../../types";
interface AccountDataEntry {
type: string;
content: Content;
}
export class AccountDataStore { export class AccountDataStore {
constructor(store) { private _store: Store<AccountDataEntry>;
this._store = store;
}
async get(type) { constructor(store: Store<AccountDataEntry>) {
return await this._store.get(type); this._store = store;
} }
set(event) { async get(type: string): Promise<AccountDataEntry | null> {
return this._store.put(event); return await this._store.get(type);
} }
set(event: AccountDataEntry): void {
this._store.put(event);
}
} }

View file

@ -15,33 +15,46 @@ limitations under the License.
*/ */
import {MAX_UNICODE, MIN_UNICODE} from "./common"; import {MAX_UNICODE, MIN_UNICODE} from "./common";
import {Store} from "../Store";
function encodeKey(userId, deviceId) { interface DeviceIdentity {
userId: string;
deviceId: string;
ed25519Key: string;
curve25519Key: string;
algorithms: string[];
displayName: string;
key: string;
}
function encodeKey(userId: string, deviceId: string): string {
return `${userId}|${deviceId}`; return `${userId}|${deviceId}`;
} }
function decodeKey(key) { function decodeKey(key: string): { userId: string, deviceId: string } {
const [userId, deviceId] = key.split("|"); const [userId, deviceId] = key.split("|");
return {userId, deviceId}; return {userId, deviceId};
} }
export class DeviceIdentityStore { export class DeviceIdentityStore {
constructor(store) { private _store: Store<DeviceIdentity>;
constructor(store: Store<DeviceIdentity>) {
this._store = store; this._store = store;
} }
getAllForUserId(userId) { getAllForUserId(userId: string): Promise<DeviceIdentity[]> {
const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, ""));
return this._store.selectWhile(range, device => { return this._store.selectWhile(range, device => {
return device.userId === userId; return device.userId === userId;
}); });
} }
async getAllDeviceIds(userId) { async getAllDeviceIds(userId: string): Promise<string[]> {
const deviceIds = []; const deviceIds: string[] = [];
const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, ""));
await this._store.iterateKeys(range, key => { await this._store.iterateKeys(range, key => {
const decodedKey = decodeKey(key); const decodedKey = decodeKey(key as string);
// prevent running into the next room // prevent running into the next room
if (decodedKey.userId === userId) { if (decodedKey.userId === userId) {
deviceIds.push(decodedKey.deviceId); deviceIds.push(decodedKey.deviceId);
@ -52,27 +65,27 @@ export class DeviceIdentityStore {
return deviceIds; return deviceIds;
} }
get(userId, deviceId) { get(userId: string, deviceId: string): Promise<DeviceIdentity | null> {
return this._store.get(encodeKey(userId, deviceId)); return this._store.get(encodeKey(userId, deviceId));
} }
set(deviceIdentity) { set(deviceIdentity: DeviceIdentity): void {
deviceIdentity.key = encodeKey(deviceIdentity.userId, deviceIdentity.deviceId); deviceIdentity.key = encodeKey(deviceIdentity.userId, deviceIdentity.deviceId);
this._store.put(deviceIdentity); this._store.put(deviceIdentity);
} }
getByCurve25519Key(curve25519Key) { getByCurve25519Key(curve25519Key: string): Promise<DeviceIdentity | null> {
return this._store.index("byCurve25519Key").get(curve25519Key); return this._store.index("byCurve25519Key").get(curve25519Key);
} }
remove(userId, deviceId) { remove(userId: string, deviceId: string): Promise<undefined> {
this._store.delete(encodeKey(userId, deviceId)); return this._store.delete(encodeKey(userId, deviceId));
} }
removeAllForUser(userId) { removeAllForUser(userId: string): Promise<undefined> {
// exclude both keys as they are theoretical min and max, // exclude both keys as they are theoretical min and max,
// but we should't have a match for just the room id, or room id with max // but we should't have a match for just the room id, or room id with max
const range = this._store.IDBKeyRange.bound(encodeKey(userId, MIN_UNICODE), encodeKey(userId, MAX_UNICODE), true, true); const range = this._store.IDBKeyRange.bound(encodeKey(userId, MIN_UNICODE), encodeKey(userId, MAX_UNICODE), true, true);
this._store.delete(range); return this._store.delete(range);
} }
} }

View file

@ -15,30 +15,40 @@ limitations under the License.
*/ */
import {MIN_UNICODE, MAX_UNICODE} from "./common"; import {MIN_UNICODE, MAX_UNICODE} from "./common";
import {Store} from "../Store";
function encodeKey(roomId, sessionId, messageIndex) { function encodeKey(roomId: string, sessionId: string, messageIndex: number | string): string {
return `${roomId}|${sessionId}|${messageIndex}`; return `${roomId}|${sessionId}|${messageIndex}`;
} }
interface GroupSessionDecryption {
eventId: string;
timestamp: number;
}
type GroupSessionEntry = GroupSessionDecryption & { key: string }
export class GroupSessionDecryptionStore { export class GroupSessionDecryptionStore {
constructor(store) { private _store: Store<GroupSessionEntry>;
constructor(store: Store<GroupSessionEntry>) {
this._store = store; this._store = store;
} }
get(roomId, sessionId, messageIndex) { get(roomId: string, sessionId: string, messageIndex: number): Promise<GroupSessionDecryption | null> {
return this._store.get(encodeKey(roomId, sessionId, messageIndex)); return this._store.get(encodeKey(roomId, sessionId, messageIndex));
} }
set(roomId, sessionId, messageIndex, decryption) { set(roomId: string, sessionId: string, messageIndex: number, decryption: GroupSessionDecryption): void {
decryption.key = encodeKey(roomId, sessionId, messageIndex); (decryption as GroupSessionEntry).key = encodeKey(roomId, sessionId, messageIndex);
this._store.put(decryption); this._store.put(decryption as GroupSessionEntry);
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId: string): Promise<undefined> {
const range = this._store.IDBKeyRange.bound( const range = this._store.IDBKeyRange.bound(
encodeKey(roomId, MIN_UNICODE, MIN_UNICODE), encodeKey(roomId, MIN_UNICODE, MIN_UNICODE),
encodeKey(roomId, MAX_UNICODE, MAX_UNICODE) encodeKey(roomId, MAX_UNICODE, MAX_UNICODE)
); );
this._store.delete(range); return this._store.delete(range);
} }
} }

View file

@ -15,36 +15,49 @@ limitations under the License.
*/ */
import {MIN_UNICODE, MAX_UNICODE} from "./common"; import {MIN_UNICODE, MAX_UNICODE} from "./common";
import {Store} from "../Store";
function encodeKey(roomId, senderKey, sessionId) { interface InboundGroupSession {
roomId: string;
senderKey: string;
sessionId: string;
session?: string;
claimedKeys?: { [algorithm : string] : string };
eventIds?: string[];
key: string;
}
function encodeKey(roomId: string, senderKey: string, sessionId: string): string {
return `${roomId}|${senderKey}|${sessionId}`; return `${roomId}|${senderKey}|${sessionId}`;
} }
export class InboundGroupSessionStore { export class InboundGroupSessionStore {
constructor(store) { private _store: Store<InboundGroupSession>;
constructor(store: Store<InboundGroupSession>) {
this._store = store; this._store = store;
} }
async has(roomId, senderKey, sessionId) { async has(roomId: string, senderKey: string, sessionId: string): Promise<boolean> {
const key = encodeKey(roomId, senderKey, sessionId); const key = encodeKey(roomId, senderKey, sessionId);
const fetchedKey = await this._store.getKey(key); const fetchedKey = await this._store.getKey(key);
return key === fetchedKey; return key === fetchedKey;
} }
get(roomId, senderKey, sessionId) { get(roomId: string, senderKey: string, sessionId: string): Promise<InboundGroupSession | null> {
return this._store.get(encodeKey(roomId, senderKey, sessionId)); return this._store.get(encodeKey(roomId, senderKey, sessionId));
} }
set(session) { set(session: InboundGroupSession): void {
session.key = encodeKey(session.roomId, session.senderKey, session.sessionId); session.key = encodeKey(session.roomId, session.senderKey, session.sessionId);
this._store.put(session); this._store.put(session);
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId: string): Promise<undefined> {
const range = this._store.IDBKeyRange.bound( const range = this._store.IDBKeyRange.bound(
encodeKey(roomId, MIN_UNICODE, MIN_UNICODE), encodeKey(roomId, MIN_UNICODE, MIN_UNICODE),
encodeKey(roomId, MAX_UNICODE, MAX_UNICODE) encodeKey(roomId, MAX_UNICODE, MAX_UNICODE)
); );
this._store.delete(range); return this._store.delete(range);
} }
} }

View file

@ -13,26 +13,38 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Store} from "../Store";
function encodeKey(senderKey, sessionId) { function encodeKey(senderKey: string, sessionId: string): string {
return `${senderKey}|${sessionId}`; return `${senderKey}|${sessionId}`;
} }
function decodeKey(key) { function decodeKey(key: string): { senderKey: string, sessionId: string } {
const [senderKey, sessionId] = key.split("|"); const [senderKey, sessionId] = key.split("|");
return {senderKey, sessionId}; return {senderKey, sessionId};
} }
interface OlmSession {
session: string;
sessionId: string;
senderKey: string;
lastUsed: number;
}
type OlmSessionEntry = OlmSession & { key: string };
export class OlmSessionStore { export class OlmSessionStore {
constructor(store) { private _store: Store<OlmSessionEntry>;
constructor(store: Store<OlmSessionEntry>) {
this._store = store; this._store = store;
} }
async getSessionIds(senderKey) { async getSessionIds(senderKey: string): Promise<string[]> {
const sessionIds = []; const sessionIds: string[] = [];
const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
await this._store.iterateKeys(range, key => { await this._store.iterateKeys(range, key => {
const decodedKey = decodeKey(key); const decodedKey = decodeKey(key as string);
// prevent running into the next room // prevent running into the next room
if (decodedKey.senderKey === senderKey) { if (decodedKey.senderKey === senderKey) {
sessionIds.push(decodedKey.sessionId); sessionIds.push(decodedKey.sessionId);
@ -43,23 +55,23 @@ export class OlmSessionStore {
return sessionIds; return sessionIds;
} }
getAll(senderKey) { getAll(senderKey: string): Promise<OlmSession[]> {
const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
return this._store.selectWhile(range, session => { return this._store.selectWhile(range, session => {
return session.senderKey === senderKey; return session.senderKey === senderKey;
}); });
} }
get(senderKey, sessionId) { get(senderKey: string, sessionId: string): Promise<OlmSession | null> {
return this._store.get(encodeKey(senderKey, sessionId)); return this._store.get(encodeKey(senderKey, sessionId));
} }
set(session) { set(session: OlmSession): void {
session.key = encodeKey(session.senderKey, session.sessionId); (session as OlmSessionEntry).key = encodeKey(session.senderKey, session.sessionId);
return this._store.put(session); this._store.put(session as OlmSessionEntry);
} }
remove(senderKey, sessionId) { remove(senderKey: string, sessionId: string): Promise<undefined> {
return this._store.delete(encodeKey(senderKey, sessionId)); return this._store.delete(encodeKey(senderKey, sessionId));
} }
} }

View file

@ -14,23 +14,46 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {MIN_UNICODE, MAX_UNICODE} from "./common"; import {MIN_UNICODE, MAX_UNICODE} from "./common";
import {Store} from "../Store";
export function encodeScopeTypeKey(scope, type) { export function encodeScopeTypeKey(scope: string, type: string): string {
return `${scope}|${type}`; return `${scope}|${type}`;
} }
interface BaseOperation {
id: string;
scope: string;
userIds: string[];
}
type OperationType = { type: "share_room_key"; roomKeyMessage: RoomKeyMessage; }
type Operation = BaseOperation & OperationType
type OperationEntry = Operation & { scopeTypeKey: string; }
interface RoomKeyMessage {
room_id: string;
session_id: string;
session_key: string;
algorithm: string;
chain_index: number;
}
export class OperationStore { export class OperationStore {
constructor(store) { private _store: Store<OperationEntry>;
constructor(store: Store<OperationEntry>) {
this._store = store; this._store = store;
} }
getAll() { getAll(): Promise<Operation[]> {
return this._store.selectAll(); return this._store.selectAll();
} }
async getAllByTypeAndScope(type, scope) { async getAllByTypeAndScope(type: string, scope: string): Promise<Operation[]> {
const key = encodeScopeTypeKey(scope, type); const key = encodeScopeTypeKey(scope, type);
const results = []; const results: Operation[] = [];
await this._store.index("byScopeAndType").iterateWhile(key, value => { await this._store.index("byScopeAndType").iterateWhile(key, value => {
if (value.scopeTypeKey !== key) { if (value.scopeTypeKey !== key) {
return false; return false;
@ -41,20 +64,20 @@ export class OperationStore {
return results; return results;
} }
add(operation) { add(operation: Operation): void {
operation.scopeTypeKey = encodeScopeTypeKey(operation.scope, operation.type); (operation as OperationEntry).scopeTypeKey = encodeScopeTypeKey(operation.scope, operation.type);
this._store.add(operation); this._store.add(operation as OperationEntry);
} }
update(operation) { update(operation: Operation): void {
this._store.put(operation); this._store.put(operation as OperationEntry);
} }
remove(id) { remove(id: string): Promise<undefined> {
this._store.delete(id); return this._store.delete(id);
} }
async removeAllForScope(scope) { async removeAllForScope(scope: string): Promise<undefined> {
const range = this._store.IDBKeyRange.bound( const range = this._store.IDBKeyRange.bound(
encodeScopeTypeKey(scope, MIN_UNICODE), encodeScopeTypeKey(scope, MIN_UNICODE),
encodeScopeTypeKey(scope, MAX_UNICODE) encodeScopeTypeKey(scope, MAX_UNICODE)
@ -64,5 +87,6 @@ export class OperationStore {
cur.delete(); cur.delete();
return true; return true;
}); });
return;
} }
} }

View file

@ -13,21 +13,30 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Store} from "../Store";
interface OutboundSession {
roomId: string;
session: string;
createdAt: number;
}
export class OutboundGroupSessionStore { export class OutboundGroupSessionStore {
constructor(store) { private _store: Store<OutboundSession>;
constructor(store: Store<OutboundSession>) {
this._store = store; this._store = store;
} }
remove(roomId) { remove(roomId: string): Promise<undefined> {
this._store.delete(roomId); return this._store.delete(roomId);
} }
get(roomId) { get(roomId: string): Promise<OutboundSession | null> {
return this._store.get(roomId); return this._store.get(roomId);
} }
set(session) { set(session: OutboundSession): void {
this._store.put(session); this._store.put(session);
} }
} }

View file

@ -16,23 +16,40 @@ limitations under the License.
import { encodeUint32, decodeUint32 } from "../utils"; import { encodeUint32, decodeUint32 } from "../utils";
import {KeyLimits} from "../../common"; import {KeyLimits} from "../../common";
import {Store} from "../Store";
import {Content} from "../../types";
function encodeKey(roomId, queueIndex) { interface PendingEntry {
roomId: string;
queueIndex: number;
eventType: string;
content: Content;
relatexTxnId: string | null;
relatedEventId: string | null;
txnId?: string;
needsEncryption: boolean;
needsUpload: boolean;
key: string;
}
function encodeKey(roomId: string, queueIndex: number): string {
return `${roomId}|${encodeUint32(queueIndex)}`; return `${roomId}|${encodeUint32(queueIndex)}`;
} }
function decodeKey(key) { function decodeKey(key: string): { roomId: string, queueIndex: number } {
const [roomId, encodedQueueIndex] = key.split("|"); const [roomId, encodedQueueIndex] = key.split("|");
const queueIndex = decodeUint32(encodedQueueIndex); const queueIndex = decodeUint32(encodedQueueIndex);
return {roomId, queueIndex}; return {roomId, queueIndex};
} }
export class PendingEventStore { export class PendingEventStore {
constructor(eventStore) { private _eventStore: Store<PendingEntry>;
constructor(eventStore: Store<PendingEntry>) {
this._eventStore = eventStore; this._eventStore = eventStore;
} }
async getMaxQueueIndex(roomId) { async getMaxQueueIndex(roomId: string): Promise<number | undefined> {
const range = this._eventStore.IDBKeyRange.bound( const range = this._eventStore.IDBKeyRange.bound(
encodeKey(roomId, KeyLimits.minStorageKey), encodeKey(roomId, KeyLimits.minStorageKey),
encodeKey(roomId, KeyLimits.maxStorageKey), encodeKey(roomId, KeyLimits.maxStorageKey),
@ -41,38 +58,38 @@ export class PendingEventStore {
); );
const maxKey = await this._eventStore.findMaxKey(range); const maxKey = await this._eventStore.findMaxKey(range);
if (maxKey) { if (maxKey) {
return decodeKey(maxKey).queueIndex; return decodeKey(maxKey as string).queueIndex;
} }
} }
remove(roomId, queueIndex) { remove(roomId: string, queueIndex: number): Promise<undefined> {
const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex)); const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex));
this._eventStore.delete(keyRange); return this._eventStore.delete(keyRange);
} }
async exists(roomId, queueIndex) { async exists(roomId: string, queueIndex: number): Promise<boolean> {
const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex)); const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex));
const key = await this._eventStore.getKey(keyRange); const key = await this._eventStore.getKey(keyRange);
return !!key; return !!key;
} }
add(pendingEvent) { add(pendingEvent: PendingEntry): void {
pendingEvent.key = encodeKey(pendingEvent.roomId, pendingEvent.queueIndex); pendingEvent.key = encodeKey(pendingEvent.roomId, pendingEvent.queueIndex);
this._eventStore.add(pendingEvent); this._eventStore.add(pendingEvent);
} }
update(pendingEvent) { update(pendingEvent: PendingEntry): void {
this._eventStore.put(pendingEvent); this._eventStore.put(pendingEvent);
} }
getAll() { getAll(): Promise<PendingEntry[]> {
return this._eventStore.selectAll(); return this._eventStore.selectAll();
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId: string): Promise<undefined> {
const minKey = encodeKey(roomId, KeyLimits.minStorageKey); const minKey = encodeKey(roomId, KeyLimits.minStorageKey);
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey); const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey);
const range = this._eventStore.IDBKeyRange.bound(minKey, maxKey); const range = this._eventStore.IDBKeyRange.bound(minKey, maxKey);
this._eventStore.delete(range); return this._eventStore.delete(range);
} }
} }

View file

@ -52,7 +52,7 @@ export class RoomMemberStore {
set(member: MemberData): void { set(member: MemberData): void {
// Object.assign would be more typesafe, but small objects // Object.assign would be more typesafe, but small objects
(member as any).key = encodeKey(member.roomId, member.userId); (member as MemberStorageEntry).key = encodeKey(member.roomId, member.userId);
this._roomMembersStore.put(member as MemberStorageEntry); this._roomMembersStore.put(member as MemberStorageEntry);
} }

View file

@ -16,31 +16,41 @@ limitations under the License.
*/ */
import {MAX_UNICODE} from "./common"; import {MAX_UNICODE} from "./common";
import {Store} from "../Store";
import {StateEvent} from "../../types";
function encodeKey(roomId, eventType, stateKey) { function encodeKey(roomId: string, eventType: string, stateKey: string) {
return `${roomId}|${eventType}|${stateKey}`; return `${roomId}|${eventType}|${stateKey}`;
} }
export interface RoomStateEntry {
roomId: string;
event: StateEvent;
key: string;
}
export class RoomStateStore { export class RoomStateStore {
constructor(idbStore) { private _roomStateStore: Store<RoomStateEntry>;
constructor(idbStore: Store<RoomStateEntry>) {
this._roomStateStore = idbStore; this._roomStateStore = idbStore;
} }
get(roomId, type, stateKey) { get(roomId: string, type: string, stateKey: string): Promise<RoomStateEntry | null> {
const key = encodeKey(roomId, type, stateKey); const key = encodeKey(roomId, type, stateKey);
return this._roomStateStore.get(key); return this._roomStateStore.get(key);
} }
set(roomId, event) { set(roomId: string, event: StateEvent): void {
const key = encodeKey(roomId, event.type, event.state_key); const key = encodeKey(roomId, event.type, event.state_key);
const entry = {roomId, event, key}; const entry = {roomId, event, key};
return this._roomStateStore.put(entry); this._roomStateStore.put(entry);
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId: string): Promise<undefined> {
// exclude both keys as they are theoretical min and max, // exclude both keys as they are theoretical min and max,
// but we should't have a match for just the room id, or room id with max // but we should't have a match for just the room id, or room id with max
const range = this._roomStateStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true); const range = this._roomStateStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
this._roomStateStore.delete(range); return this._roomStateStore.delete(range);
} }
} }

View file

@ -27,7 +27,7 @@ interface Annotation {
firstTimestamp: number; firstTimestamp: number;
} }
interface StorageEntry { interface TimelineEventEntry {
roomId: string; roomId: string;
fragmentId: number; fragmentId: number;
eventIndex: number; eventIndex: number;
@ -35,10 +35,10 @@ interface StorageEntry {
displayName?: string; displayName?: string;
avatarUrl?: string; avatarUrl?: string;
annotations?: { [key : string]: Annotation }; annotations?: { [key : string]: Annotation };
key: string;
eventIdKey: string;
} }
type TimelineEventStorageEntry = TimelineEventEntry & { key: string, eventIdKey: string };
function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string { function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string {
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`; return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
} }
@ -126,9 +126,9 @@ class Range {
* @property {?Gap} gap if a gap entry, the gap * @property {?Gap} gap if a gap entry, the gap
*/ */
export class TimelineEventStore { export class TimelineEventStore {
private _timelineStore: Store<StorageEntry>; private _timelineStore: Store<TimelineEventStorageEntry>;
constructor(timelineStore: Store<StorageEntry>) { constructor(timelineStore: Store<TimelineEventStorageEntry>) {
this._timelineStore = timelineStore; this._timelineStore = timelineStore;
} }
@ -175,7 +175,7 @@ export class TimelineEventStore {
* @param amount * @param amount
* @return a promise resolving to an array with 0 or more entries, in ascending order. * @return a promise resolving to an array with 0 or more entries, in ascending order.
*/ */
async lastEvents(roomId: string, fragmentId: number, amount: number): Promise<StorageEntry[]> { async lastEvents(roomId: string, fragmentId: number, amount: number): Promise<TimelineEventEntry[]> {
const eventKey = EventKey.maxKey; const eventKey = EventKey.maxKey;
eventKey.fragmentId = fragmentId; eventKey.fragmentId = fragmentId;
return this.eventsBefore(roomId, eventKey, amount); return this.eventsBefore(roomId, eventKey, amount);
@ -187,7 +187,7 @@ export class TimelineEventStore {
* @param amount * @param amount
* @return a promise resolving to an array with 0 or more entries, in ascending order. * @return a promise resolving to an array with 0 or more entries, in ascending order.
*/ */
async firstEvents(roomId: string, fragmentId: number, amount: number): Promise<StorageEntry[]> { async firstEvents(roomId: string, fragmentId: number, amount: number): Promise<TimelineEventEntry[]> {
const eventKey = EventKey.minKey; const eventKey = EventKey.minKey;
eventKey.fragmentId = fragmentId; eventKey.fragmentId = fragmentId;
return this.eventsAfter(roomId, eventKey, amount); return this.eventsAfter(roomId, eventKey, amount);
@ -200,7 +200,7 @@ export class TimelineEventStore {
* @param amount * @param amount
* @return a promise resolving to an array with 0 or more entries, in ascending order. * @return a promise resolving to an array with 0 or more entries, in ascending order.
*/ */
eventsAfter(roomId: string, eventKey: EventKey, amount: number): Promise<StorageEntry[]> { eventsAfter(roomId: string, eventKey: EventKey, amount: number): Promise<TimelineEventEntry[]> {
const idbRange = this.lowerBoundRange(eventKey, true).asIDBKeyRange(roomId); const idbRange = this.lowerBoundRange(eventKey, true).asIDBKeyRange(roomId);
return this._timelineStore.selectLimit(idbRange, amount); return this._timelineStore.selectLimit(idbRange, amount);
} }
@ -212,7 +212,7 @@ export class TimelineEventStore {
* @param amount * @param amount
* @return a promise resolving to an array with 0 or more entries, in ascending order. * @return a promise resolving to an array with 0 or more entries, in ascending order.
*/ */
async eventsBefore(roomId: string, eventKey: EventKey, amount: number): Promise<StorageEntry[]> { async eventsBefore(roomId: string, eventKey: EventKey, amount: number): Promise<TimelineEventEntry[]> {
const range = this.upperBoundRange(eventKey, true).asIDBKeyRange(roomId); const range = this.upperBoundRange(eventKey, true).asIDBKeyRange(roomId);
const events = await this._timelineStore.selectLimitReverse(range, amount); const events = await this._timelineStore.selectLimitReverse(range, amount);
events.reverse(); // because we fetched them backwards events.reverse(); // because we fetched them backwards
@ -265,11 +265,11 @@ export class TimelineEventStore {
* @return nothing. To wait for the operation to finish, await the transaction it's part of. * @return nothing. To wait for the operation to finish, await the transaction it's part of.
* @throws {StorageError} ... * @throws {StorageError} ...
*/ */
insert(entry: StorageEntry): void { insert(entry: TimelineEventEntry): void {
entry.key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex); (entry as TimelineEventStorageEntry).key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex);
entry.eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id); (entry as TimelineEventStorageEntry).eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id);
// TODO: map error? or in idb/store? // TODO: map error? or in idb/store?
this._timelineStore.add(entry); this._timelineStore.add(entry as TimelineEventStorageEntry);
} }
/** Updates the entry into the store with the given [roomId, eventKey] combination. /** Updates the entry into the store with the given [roomId, eventKey] combination.
@ -277,15 +277,15 @@ export class TimelineEventStore {
* @param entry the entry to update. * @param entry the entry to update.
* @return nothing. To wait for the operation to finish, await the transaction it's part of. * @return nothing. To wait for the operation to finish, await the transaction it's part of.
*/ */
update(entry: StorageEntry): void { update(entry: TimelineEventEntry): void {
this._timelineStore.put(entry); this._timelineStore.put(entry as TimelineEventStorageEntry);
} }
get(roomId: string, eventKey: EventKey): Promise<StorageEntry | null> { get(roomId: string, eventKey: EventKey): Promise<TimelineEventEntry | null> {
return this._timelineStore.get(encodeKey(roomId, eventKey.fragmentId, eventKey.eventIndex)); return this._timelineStore.get(encodeKey(roomId, eventKey.fragmentId, eventKey.eventIndex));
} }
getByEventId(roomId: string, eventId: string): Promise<StorageEntry | null> { getByEventId(roomId: string, eventId: string): Promise<TimelineEventEntry | null> {
return this._timelineStore.index("byEventId").get(encodeEventIdKey(roomId, eventId)); return this._timelineStore.index("byEventId").get(encodeEventIdKey(roomId, eventId));
} }

View file

@ -17,17 +17,31 @@ limitations under the License.
import { StorageError } from "../../common"; import { StorageError } from "../../common";
import {KeyLimits} from "../../common"; import {KeyLimits} from "../../common";
import { encodeUint32 } from "../utils"; import { encodeUint32 } from "../utils";
import {Store} from "../Store";
function encodeKey(roomId, fragmentId) { interface Fragment {
roomId: string;
id: number;
previousId: number | null;
nextId: number | null;
previousToken: string | null;
nextToken: string | null;
}
type FragmentEntry = Fragment & { key: string }
function encodeKey(roomId: string, fragmentId: number): string {
return `${roomId}|${encodeUint32(fragmentId)}`; return `${roomId}|${encodeUint32(fragmentId)}`;
} }
export class TimelineFragmentStore { export class TimelineFragmentStore {
constructor(store) { private _store: Store<FragmentEntry>;
constructor(store: Store<FragmentEntry>) {
this._store = store; this._store = store;
} }
_allRange(roomId) { _allRange(roomId: string): IDBKeyRange {
try { try {
return this._store.IDBKeyRange.bound( return this._store.IDBKeyRange.bound(
encodeKey(roomId, KeyLimits.minStorageKey), encodeKey(roomId, KeyLimits.minStorageKey),
@ -38,13 +52,13 @@ export class TimelineFragmentStore {
} }
} }
all(roomId) { all(roomId: string): Promise<FragmentEntry[]> {
return this._store.selectAll(this._allRange(roomId)); return this._store.selectAll(this._allRange(roomId));
} }
/** Returns the fragment without a nextToken and without nextId, /** Returns the fragment without a nextToken and without nextId,
if any, with the largest id if there are multiple (which should not happen) */ if any, with the largest id if there are multiple (which should not happen) */
liveFragment(roomId) { liveFragment(roomId: string): Promise<FragmentEntry | undefined> {
// why do we need this? // why do we need this?
// Ok, take the case where you've got a /context fragment and a /sync fragment // Ok, take the case where you've got a /context fragment and a /sync fragment
// They are not connected. So, upon loading the persister, which one do we take? We can't sort them ... // They are not connected. So, upon loading the persister, which one do we take? We can't sort them ...
@ -60,20 +74,20 @@ export class TimelineFragmentStore {
// should generate an id an return it? // should generate an id an return it?
// depends if we want to do anything smart with fragment ids, // depends if we want to do anything smart with fragment ids,
// like give them meaning depending on range. not for now probably ... // like give them meaning depending on range. not for now probably ...
add(fragment) { add(fragment: Fragment): void {
fragment.key = encodeKey(fragment.roomId, fragment.id); (fragment as FragmentEntry).key = encodeKey(fragment.roomId, fragment.id);
this._store.add(fragment); this._store.add(fragment as FragmentEntry);
} }
update(fragment) { update(fragment: FragmentEntry): void {
this._store.put(fragment); this._store.put(fragment);
} }
get(roomId, fragmentId) { get(roomId: string, fragmentId: number): Promise<FragmentEntry | null> {
return this._store.get(encodeKey(roomId, fragmentId)); return this._store.get(encodeKey(roomId, fragmentId));
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId: string): Promise<undefined> {
this._store.delete(this._allRange(roomId)); return this._store.delete(this._allRange(roomId));
} }
} }

View file

@ -13,21 +13,30 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Store} from "../Store";
interface UserIdentity {
userId: string;
roomIds: string[];
deviceTrackingStatus: number;
}
export class UserIdentityStore { export class UserIdentityStore {
constructor(store) { private _store: Store<UserIdentity>;
constructor(store: Store<UserIdentity>) {
this._store = store; this._store = store;
} }
get(userId) { get(userId: string): Promise<UserIdentity | null> {
return this._store.get(userId); return this._store.get(userId);
} }
set(userIdentity) { set(userIdentity: UserIdentity): void {
this._store.put(userIdentity); this._store.put(userIdentity);
} }
remove(userId) { remove(userId: string): Promise<undefined> {
return this._store.delete(userId); return this._store.delete(userId);
} }
} }