2021-01-14 18:07:05 +00:00
|
|
|
// Copyright 2020-2021 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
2021-06-15 00:09:37 +00:00
|
|
|
/* eslint-disable no-restricted-syntax */
|
2020-10-30 20:34:04 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
import {
|
|
|
|
StorageAccessType as Access,
|
|
|
|
StorageInterface,
|
|
|
|
} from '../types/Storage.d';
|
|
|
|
import { User } from './storage/User';
|
|
|
|
import { Blocked } from './storage/Blocked';
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
import { assert } from '../util/assert';
|
|
|
|
import Data from '../sql/Client';
|
|
|
|
import { SignalProtocolStore } from '../SignalProtocolStore';
|
|
|
|
|
|
|
|
export class Storage implements StorageInterface {
|
|
|
|
public readonly user: User;
|
|
|
|
|
|
|
|
public readonly blocked: Blocked;
|
|
|
|
|
|
|
|
private ready = false;
|
|
|
|
|
|
|
|
private readyCallbacks: Array<() => void> = [];
|
|
|
|
|
|
|
|
private items: Partial<Access> = Object.create(null);
|
|
|
|
|
|
|
|
private privProtocol: SignalProtocolStore | undefined;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.user = new User(this);
|
|
|
|
this.blocked = new Blocked(this);
|
|
|
|
|
|
|
|
window.storage = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
get protocol(): SignalProtocolStore {
|
|
|
|
assert(
|
|
|
|
this.privProtocol !== undefined,
|
|
|
|
'SignalProtocolStore not initialized'
|
|
|
|
);
|
|
|
|
return this.privProtocol;
|
|
|
|
}
|
|
|
|
|
|
|
|
set protocol(value: SignalProtocolStore) {
|
|
|
|
this.privProtocol = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
// `StorageInterface` implementation
|
|
|
|
|
|
|
|
public get<K extends keyof Access, V extends Access[K]>(
|
|
|
|
key: K
|
|
|
|
): V | undefined;
|
|
|
|
|
|
|
|
public get<K extends keyof Access, V extends Access[K]>(
|
|
|
|
key: K,
|
|
|
|
defaultValue: V
|
|
|
|
): V;
|
|
|
|
|
|
|
|
public get<K extends keyof Access, V extends Access[K]>(
|
|
|
|
key: K,
|
|
|
|
defaultValue?: V
|
|
|
|
): V | undefined {
|
|
|
|
if (!this.ready) {
|
|
|
|
window.log.warn('Called storage.get before storage is ready. key:', key);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
const item = this.items[key];
|
|
|
|
if (item === undefined) {
|
2020-04-13 17:37:29 +00:00
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
return item as V;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async put<K extends keyof Access>(
|
|
|
|
key: K,
|
|
|
|
value: Access[K]
|
|
|
|
): Promise<void> {
|
|
|
|
if (!this.ready) {
|
|
|
|
window.log.warn('Called storage.put before storage is ready. key:', key);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.items[key] = value;
|
|
|
|
await window.Signal.Data.createOrUpdateItem({ id: key, value });
|
|
|
|
|
|
|
|
window.reduxActions?.items.putItemExternal(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async remove<K extends keyof Access>(key: K): Promise<void> {
|
|
|
|
if (!this.ready) {
|
|
|
|
window.log.warn(
|
|
|
|
'Called storage.remove before storage is ready. key:',
|
|
|
|
key
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete this.items[key];
|
|
|
|
await Data.removeItemById(key);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
window.reduxActions?.items.removeItemExternal(key);
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
// Regular methods
|
|
|
|
|
|
|
|
public onready(callback: () => void): void {
|
|
|
|
if (this.ready) {
|
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
this.readyCallbacks.push(callback);
|
|
|
|
}
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
public async fetch(): Promise<void> {
|
|
|
|
this.reset();
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
Object.assign(this.items, await Data.getAllItems());
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
this.ready = true;
|
|
|
|
this.callListeners();
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-15 00:09:37 +00:00
|
|
|
public reset(): void {
|
|
|
|
this.ready = false;
|
|
|
|
this.items = Object.create(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public getItemsState(): Partial<Access> {
|
|
|
|
const state = Object.create(null);
|
|
|
|
|
|
|
|
// TypeScript isn't smart enough to figure out the types automatically.
|
|
|
|
const { items } = this;
|
|
|
|
const allKeys = Object.keys(items) as Array<keyof typeof items>;
|
|
|
|
|
|
|
|
for (const key of allKeys) {
|
|
|
|
state[key] = items[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
private callListeners(): void {
|
|
|
|
if (!this.ready) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const callbacks = this.readyCallbacks;
|
|
|
|
this.readyCallbacks = [];
|
|
|
|
callbacks.forEach(callback => callback());
|
|
|
|
}
|
|
|
|
}
|