Enforce stronger types for ArrayBuffers and storage

This commit is contained in:
Fedor Indutny 2021-06-14 17:09:37 -07:00 committed by GitHub
parent 61ac79e9ae
commit 8f5086227a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 748 additions and 675 deletions

View file

@ -1,51 +1,149 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/no-explicit-any */
import utils from './Helpers';
import {
StorageAccessType as Access,
StorageInterface,
} from '../types/Storage.d';
import { User } from './storage/User';
import { Blocked } from './storage/Blocked';
// Default implementation working with localStorage
const localStorageImpl: StorageInterface = {
put(key: string, value: any) {
if (value === undefined) {
throw new Error('Tried to store undefined');
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);
}
localStorage.setItem(`${key}`, utils.jsonThing(value));
},
get(key: string, defaultValue: any) {
const value = localStorage.getItem(`${key}`);
if (value === null) {
const item = this.items[key];
if (item === undefined) {
return defaultValue;
}
return JSON.parse(value);
},
remove(key: string) {
localStorage.removeItem(`${key}`);
},
};
return item as V;
}
export type StorageInterface = {
put(key: string, value: any): void | Promise<void>;
get(key: string, defaultValue: any): any;
remove(key: string): void | Promise<void>;
};
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);
}
const Storage = {
impl: localStorageImpl,
this.items[key] = value;
await window.Signal.Data.createOrUpdateItem({ id: key, value });
put(key: string, value: unknown): Promise<void> | void {
return Storage.impl.put(key, value);
},
window.reduxActions?.items.putItemExternal(key, value);
}
get(key: string, defaultValue: unknown): Promise<unknown> {
return Storage.impl.get(key, defaultValue);
},
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
);
}
remove(key: string): Promise<void> | void {
return Storage.impl.remove(key);
},
};
delete this.items[key];
await Data.removeItemById(key);
export default Storage;
window.reduxActions?.items.removeItemExternal(key);
}
// Regular methods
public onready(callback: () => void): void {
if (this.ready) {
callback();
} else {
this.readyCallbacks.push(callback);
}
}
public async fetch(): Promise<void> {
this.reset();
Object.assign(this.items, await Data.getAllItems());
this.ready = true;
this.callListeners();
}
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());
}
}