mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
web: use own kvstore instead of localforage
localforage is unmaintained and also has a huge footprint not to mention the performance issues.
This commit is contained in:
committed by
Abdullah Atta
parent
44782d552d
commit
f91ada3c48
1197
apps/web/package-lock.json
generated
1197
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,6 @@
|
|||||||
"@notesnook/crypto-worker": "*",
|
"@notesnook/crypto-worker": "*",
|
||||||
"@notesnook/desktop": "*",
|
"@notesnook/desktop": "*",
|
||||||
"@notesnook/editor": "*",
|
"@notesnook/editor": "*",
|
||||||
"@notesnook/localforage-getitems": "^1.4.4",
|
|
||||||
"@notesnook/logger": "*",
|
"@notesnook/logger": "*",
|
||||||
"@notesnook/streamable-fs": "*",
|
"@notesnook/streamable-fs": "*",
|
||||||
"@notesnook/theme": "*",
|
"@notesnook/theme": "*",
|
||||||
@@ -47,8 +46,6 @@
|
|||||||
"hash-wasm": "^4.9.0",
|
"hash-wasm": "^4.9.0",
|
||||||
"hotkeys-js": "^3.8.3",
|
"hotkeys-js": "^3.8.3",
|
||||||
"immer": "^9.0.6",
|
"immer": "^9.0.6",
|
||||||
"localforage": "^1.10.0",
|
|
||||||
"localforage-driver-memory": "^1.0.5",
|
|
||||||
"mac-scrollbar": "^0.10.3",
|
"mac-scrollbar": "^0.10.3",
|
||||||
"marked": "^4.1.0",
|
"marked": "^4.1.0",
|
||||||
"pdfjs-dist": "3.6.172",
|
"pdfjs-dist": "3.6.172",
|
||||||
@@ -81,8 +78,6 @@
|
|||||||
"@types/react": "17.0.2",
|
"@types/react": "17.0.2",
|
||||||
"@types/react-dom": "17.0.2",
|
"@types/react-dom": "17.0.2",
|
||||||
"@types/react-modal": "^3.13.1",
|
"@types/react-modal": "^3.13.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
|
||||||
"@vitejs/plugin-react-refresh": "^1.3.6",
|
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
@@ -102,7 +97,6 @@
|
|||||||
"vite-plugin-env-compatible": "^1.1.1",
|
"vite-plugin-env-compatible": "^1.1.1",
|
||||||
"vite-plugin-pwa": "^0.16.3",
|
"vite-plugin-pwa": "^0.16.3",
|
||||||
"vite-plugin-svgr": "^3.2.0",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"vite-plugin-top-level-await": "^1.3.1",
|
|
||||||
"vitest": "^0.32.0",
|
"vitest": "^0.32.0",
|
||||||
"workbox-core": "^7.0.0",
|
"workbox-core": "^7.0.0",
|
||||||
"workbox-expiration": "^7.0.0",
|
"workbox-expiration": "^7.0.0",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "web-streams-polyfill/dist/ponyfill";
|
import "web-streams-polyfill/dist/ponyfill";
|
||||||
import localforage from "localforage";
|
|
||||||
import { xxhash64, createXXHash64 } from "hash-wasm";
|
import { xxhash64, createXXHash64 } from "hash-wasm";
|
||||||
import axios, { AxiosProgressEvent } from "axios";
|
import axios, { AxiosProgressEvent } from "axios";
|
||||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||||
@@ -37,6 +36,7 @@ import { Base64DecoderStream } from "../utils/streams/base64-decoder-stream";
|
|||||||
import { toBlob } from "@notesnook-importer/core/dist/src/utils/stream";
|
import { toBlob } from "@notesnook-importer/core/dist/src/utils/stream";
|
||||||
import { Cipher, OutputFormat, SerializedKey } from "@notesnook/crypto";
|
import { Cipher, OutputFormat, SerializedKey } from "@notesnook/crypto";
|
||||||
import { IDataType } from "hash-wasm/dist/lib/util";
|
import { IDataType } from "hash-wasm/dist/lib/util";
|
||||||
|
import { IndexedDBKVStore } from "./key-value";
|
||||||
|
|
||||||
const ABYTES = 17;
|
const ABYTES = 17;
|
||||||
const CHUNK_SIZE = 512 * 1024;
|
const CHUNK_SIZE = 512 * 1024;
|
||||||
@@ -53,7 +53,7 @@ async function writeEncryptedFile(
|
|||||||
) {
|
) {
|
||||||
const crypto = await getNNCrypto();
|
const crypto = await getNNCrypto();
|
||||||
|
|
||||||
if (!localforage.supports(localforage.INDEXEDDB))
|
if (!IndexedDBKVStore.isIndexedDBSupported())
|
||||||
throw new Error("This browser does not support IndexedDB.");
|
throw new Error("This browser does not support IndexedDB.");
|
||||||
|
|
||||||
if (await streamablefs.exists(hash)) await streamablefs.deleteFile(hash);
|
if (await streamablefs.exists(hash)) await streamablefs.deleteFile(hash);
|
||||||
|
|||||||
327
apps/web/src/interfaces/key-value.ts
Normal file
327
apps/web/src/interfaces/key-value.ts
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Config from "../utils/config";
|
||||||
|
|
||||||
|
export interface IKVStore {
|
||||||
|
/**
|
||||||
|
* Get a value by its key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
get<T>(key: string): Promise<T | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value with a key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
set(key: string, value: any): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set multiple values at once. This is faster than calling set() multiple times.
|
||||||
|
* It's also atomic – if one of the pairs can't be added, none will be added.
|
||||||
|
*
|
||||||
|
* @param entries Array of entries, where each entry is an array of `[key, value]`.
|
||||||
|
*/
|
||||||
|
setMany(entries: [string, any][]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get multiple values by their keys
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
*/
|
||||||
|
getMany<T>(keys: string[]): Promise<[string, T][]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a particular key from the store.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
delete(key: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete multiple keys at once.
|
||||||
|
*
|
||||||
|
* @param keys List of keys to delete.
|
||||||
|
*/
|
||||||
|
deleteMany(keys: string[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all values in the store.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
clear(): Promise<void>;
|
||||||
|
|
||||||
|
keys(): Promise<string[]>;
|
||||||
|
values<T>(): Promise<T[]>;
|
||||||
|
entries<T>(): Promise<[string, T][]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LocalStorageKVStore implements IKVStore {
|
||||||
|
get<T>(key: string): Promise<T | undefined> {
|
||||||
|
return Promise.resolve(Config.get(key));
|
||||||
|
}
|
||||||
|
set(key: string, value: any): Promise<void> {
|
||||||
|
return Promise.resolve(Config.set(key, value));
|
||||||
|
}
|
||||||
|
setMany(entries: [string, any][]): Promise<void> {
|
||||||
|
for (const entry of entries) {
|
||||||
|
Config.set(entry[0], entry[1]);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
getMany<T>(keys: string[]): Promise<[string, T][]> {
|
||||||
|
const entries: [string, T][] = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
entries.push([key, Config.get(key)]);
|
||||||
|
}
|
||||||
|
return Promise.resolve(entries);
|
||||||
|
}
|
||||||
|
delete(key: string): Promise<void> {
|
||||||
|
return Promise.resolve(Config.remove(key));
|
||||||
|
}
|
||||||
|
deleteMany(keys: string[]): Promise<void> {
|
||||||
|
for (const key of keys) {
|
||||||
|
Config.remove(key);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
clear(): Promise<void> {
|
||||||
|
Config.clear();
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
keys(): Promise<string[]> {
|
||||||
|
return Promise.resolve(Object.keys(Config.all()));
|
||||||
|
}
|
||||||
|
values<T>(): Promise<T[]> {
|
||||||
|
return Promise.resolve(Object.values<T>(Config.all()));
|
||||||
|
}
|
||||||
|
entries<T>(): Promise<[string, T][]> {
|
||||||
|
return Promise.resolve(Object.entries<T>(Config.all()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MemoryKVStore implements IKVStore {
|
||||||
|
private storage: Record<string, any> = {};
|
||||||
|
get<T>(key: string): Promise<T | undefined> {
|
||||||
|
return Promise.resolve(this.storage[key]);
|
||||||
|
}
|
||||||
|
set(key: string, value: any): Promise<void> {
|
||||||
|
this.storage[key] = value;
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
setMany(entries: [string, any][]): Promise<void> {
|
||||||
|
for (const entry of entries) {
|
||||||
|
this.storage[entry[0]] = entry[1];
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
getMany<T>(keys: string[]): Promise<[string, T][]> {
|
||||||
|
const entries: [string, T][] = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
entries.push([key, this.storage[key]]);
|
||||||
|
}
|
||||||
|
return Promise.resolve(entries);
|
||||||
|
}
|
||||||
|
delete(key: string): Promise<void> {
|
||||||
|
delete this.storage[key];
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
deleteMany(keys: string[]): Promise<void> {
|
||||||
|
for (const key of keys) {
|
||||||
|
delete this.storage[key];
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
clear(): Promise<void> {
|
||||||
|
this.storage = {};
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
keys(): Promise<string[]> {
|
||||||
|
return Promise.resolve(Object.keys(this.storage));
|
||||||
|
}
|
||||||
|
values<T>(): Promise<T[]> {
|
||||||
|
return Promise.resolve(Object.values<T>(this.storage));
|
||||||
|
}
|
||||||
|
entries<T>(): Promise<[string, T][]> {
|
||||||
|
return Promise.resolve(Object.entries<T>(this.storage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseStore = <T>(
|
||||||
|
txMode: IDBTransactionMode,
|
||||||
|
callback: (store: IDBObjectStore) => T | PromiseLike<T>
|
||||||
|
) => Promise<T>;
|
||||||
|
|
||||||
|
export class IndexedDBKVStore implements IKVStore {
|
||||||
|
store: UseStore;
|
||||||
|
|
||||||
|
static isIndexedDBSupported(): boolean {
|
||||||
|
return "indexedDB" in window;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(databaseName: string, storeName: string) {
|
||||||
|
this.store = this.createStore(databaseName, storeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private promisifyRequest<T = undefined>(
|
||||||
|
request: IDBRequest<T> | IDBTransaction
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
// @ts-ignore - file size hacks
|
||||||
|
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
||||||
|
// @ts-ignore - file size hacks
|
||||||
|
request.onabort = request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createStore(dbName: string, storeName: string): UseStore {
|
||||||
|
const request = indexedDB.open(dbName);
|
||||||
|
request.onupgradeneeded = () => request.result.createObjectStore(storeName);
|
||||||
|
const dbp = this.promisifyRequest(request);
|
||||||
|
|
||||||
|
return (txMode, callback) =>
|
||||||
|
dbp.then((db) =>
|
||||||
|
callback(db.transaction(storeName, txMode).objectStore(storeName))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private eachCursor(
|
||||||
|
store: IDBObjectStore,
|
||||||
|
callback: (cursor: IDBCursorWithValue) => void
|
||||||
|
): Promise<void> {
|
||||||
|
store.openCursor().onsuccess = function () {
|
||||||
|
if (!this.result) return;
|
||||||
|
callback(this.result);
|
||||||
|
this.result.continue();
|
||||||
|
};
|
||||||
|
return this.promisifyRequest(store.transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
get<T>(key: string): Promise<T | undefined> {
|
||||||
|
return this.store("readonly", (store) =>
|
||||||
|
this.promisifyRequest(store.get(key))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: string, value: any): Promise<void> {
|
||||||
|
return this.store("readwrite", (store) => {
|
||||||
|
store.put(value, key);
|
||||||
|
return this.promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMany(entries: [string, any][]): Promise<void> {
|
||||||
|
return this.store("readwrite", (store) => {
|
||||||
|
entries.forEach((entry) => store.put(entry[1], entry[0]));
|
||||||
|
return this.promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMany<T>(keys: string[]): Promise<[string, T][]> {
|
||||||
|
return this.store("readonly", (store) =>
|
||||||
|
Promise.all(
|
||||||
|
keys.map(async (key) => [
|
||||||
|
key,
|
||||||
|
await this.promisifyRequest(store.get(key))
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key: string): Promise<void> {
|
||||||
|
return this.store("readwrite", (store) => {
|
||||||
|
store.delete(key);
|
||||||
|
return this.promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMany(keys: string[]): Promise<void> {
|
||||||
|
return this.store("readwrite", (store: IDBObjectStore) => {
|
||||||
|
keys.forEach((key: IDBValidKey) => store.delete(key));
|
||||||
|
return this.promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): Promise<void> {
|
||||||
|
return this.store("readwrite", (store) => {
|
||||||
|
store.clear();
|
||||||
|
return this.promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
keys<KeyType extends IDBValidKey>(): Promise<KeyType[]> {
|
||||||
|
return this.store("readonly", (store) => {
|
||||||
|
// Fast path for modern browsers
|
||||||
|
if (store.getAllKeys) {
|
||||||
|
return this.promisifyRequest(
|
||||||
|
store.getAllKeys() as unknown as IDBRequest<KeyType[]>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: KeyType[] = [];
|
||||||
|
|
||||||
|
return this.eachCursor(store, (cursor) =>
|
||||||
|
items.push(cursor.key as KeyType)
|
||||||
|
).then(() => items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
values<T = any>(): Promise<T[]> {
|
||||||
|
return this.store("readonly", (store) => {
|
||||||
|
// Fast path for modern browsers
|
||||||
|
if (store.getAll) {
|
||||||
|
return this.promisifyRequest(store.getAll() as IDBRequest<T[]>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: T[] = [];
|
||||||
|
|
||||||
|
return this.eachCursor(store, (cursor) =>
|
||||||
|
items.push(cursor.value as T)
|
||||||
|
).then(() => items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entries<KeyType extends IDBValidKey, ValueType = any>(): Promise<
|
||||||
|
[KeyType, ValueType][]
|
||||||
|
> {
|
||||||
|
return this.store("readonly", (store) => {
|
||||||
|
// Fast path for modern browsers
|
||||||
|
// (although, hopefully we'll get a simpler path some day)
|
||||||
|
if (store.getAll && store.getAllKeys) {
|
||||||
|
return Promise.all([
|
||||||
|
this.promisifyRequest(
|
||||||
|
store.getAllKeys() as unknown as IDBRequest<KeyType[]>
|
||||||
|
),
|
||||||
|
this.promisifyRequest(store.getAll() as IDBRequest<ValueType[]>)
|
||||||
|
]).then(([keys, values]) => keys.map((key, i) => [key, values[i]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: [KeyType, ValueType][] = [];
|
||||||
|
|
||||||
|
return this.store("readonly", (store) =>
|
||||||
|
this.eachCursor(store, (cursor) =>
|
||||||
|
items.push([cursor.key as KeyType, cursor.value])
|
||||||
|
).then(() => items)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,50 +17,49 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import localforage from "localforage";
|
import {
|
||||||
import { extendPrototype } from "@notesnook/localforage-getitems";
|
IndexedDBKVStore,
|
||||||
import * as MemoryDriver from "localforage-driver-memory";
|
LocalStorageKVStore,
|
||||||
|
MemoryKVStore,
|
||||||
|
IKVStore
|
||||||
|
} from "./key-value";
|
||||||
import { getNNCrypto } from "./nncrypto.stub";
|
import { getNNCrypto } from "./nncrypto.stub";
|
||||||
import type { Cipher, SerializedKey } from "@notesnook/crypto/dist/src/types";
|
import type { Cipher, SerializedKey } from "@notesnook/crypto/dist/src/types";
|
||||||
|
|
||||||
localforage.defineDriver(MemoryDriver);
|
|
||||||
extendPrototype(localforage);
|
|
||||||
|
|
||||||
type EncryptedKey = { iv: Uint8Array; cipher: BufferSource };
|
type EncryptedKey = { iv: Uint8Array; cipher: BufferSource };
|
||||||
export type DatabasePersistence = "memory" | "db";
|
export type DatabasePersistence = "memory" | "db";
|
||||||
|
|
||||||
const APP_SALT = "oVzKtazBo7d8sb7TBvY9jw";
|
const APP_SALT = "oVzKtazBo7d8sb7TBvY9jw";
|
||||||
|
|
||||||
|
|
||||||
export class NNStorage {
|
export class NNStorage {
|
||||||
database: LocalForage;
|
database: IKVStore;
|
||||||
|
|
||||||
constructor(name: string, persistence: DatabasePersistence = "db") {
|
constructor(name: string, persistence: DatabasePersistence = "db") {
|
||||||
const drivers =
|
this.database =
|
||||||
persistence === "memory"
|
persistence === "memory"
|
||||||
? [MemoryDriver._driver]
|
? new MemoryKVStore()
|
||||||
: [localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE];
|
: IndexedDBKVStore.isIndexedDBSupported()
|
||||||
this.database = localforage.createInstance({
|
? new IndexedDBKVStore(name, "keyvaluepairs")
|
||||||
name,
|
: new LocalStorageKVStore();
|
||||||
driver: drivers
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
read<T>(key: string): Promise<T | null> {
|
read<T>(key: string): Promise<T | undefined> {
|
||||||
if (!key) return Promise.resolve(null);
|
if (!key) return Promise.resolve(undefined);
|
||||||
return this.database.getItem(key);
|
return this.database.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
readMulti(keys: string[]) {
|
readMulti(keys: string[]) {
|
||||||
if (keys.length <= 0) return [];
|
if (keys.length <= 0) return [];
|
||||||
return this.database.getItems(keys.sort());
|
return this.database.getMany(keys.sort());
|
||||||
}
|
}
|
||||||
|
|
||||||
write<T>(key: string, data: T) {
|
write<T>(key: string, data: T) {
|
||||||
return this.database.setItem(key, data);
|
return this.database.set(key, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(key: string) {
|
remove(key: string) {
|
||||||
return this.database.removeItem(key);
|
return this.database.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
@@ -78,7 +77,11 @@ export class NNStorage {
|
|||||||
const crypto = await getNNCrypto();
|
const crypto = await getNNCrypto();
|
||||||
const keyData = await crypto.exportKey(password, salt);
|
const keyData = await crypto.exportKey(password, salt);
|
||||||
|
|
||||||
if (this.isIndexedDBSupported() && window?.crypto?.subtle && keyData.key) {
|
if (
|
||||||
|
IndexedDBKVStore.isIndexedDBSupported() &&
|
||||||
|
window?.crypto?.subtle &&
|
||||||
|
keyData.key
|
||||||
|
) {
|
||||||
const pbkdfKey = await derivePBKDF2Key(password);
|
const pbkdfKey = await derivePBKDF2Key(password);
|
||||||
await this.write(name, pbkdfKey);
|
await this.write(name, pbkdfKey);
|
||||||
const cipheredKey = await aesEncrypt(pbkdfKey, keyData.key);
|
const cipheredKey = await aesEncrypt(pbkdfKey, keyData.key);
|
||||||
@@ -91,7 +94,7 @@ export class NNStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCryptoKey(name: string): Promise<string | undefined> {
|
async getCryptoKey(name: string): Promise<string | undefined> {
|
||||||
if (this.isIndexedDBSupported() && window?.crypto?.subtle) {
|
if (IndexedDBKVStore.isIndexedDBSupported() && window?.crypto?.subtle) {
|
||||||
const pbkdfKey = await this.read<CryptoKey>(name);
|
const pbkdfKey = await this.read<CryptoKey>(name);
|
||||||
const cipheredKey = await this.read<EncryptedKey | string>(`${name}@_k`);
|
const cipheredKey = await this.read<EncryptedKey | string>(`${name}@_k`);
|
||||||
if (typeof cipheredKey === "string") return cipheredKey;
|
if (typeof cipheredKey === "string") return cipheredKey;
|
||||||
@@ -104,10 +107,6 @@ export class NNStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isIndexedDBSupported(): boolean {
|
|
||||||
return this.database.driver() === "asyncStorage";
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateCryptoKey(
|
async generateCryptoKey(
|
||||||
password: string,
|
password: string,
|
||||||
salt?: string
|
salt?: string
|
||||||
|
|||||||
@@ -29,12 +29,16 @@ function get<T>(key: string, def?: T): T {
|
|||||||
return value ? tryParse(value) : def;
|
return value ? tryParse(value) : def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remove(key: string) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function all(): Record<string, unknown> {
|
function all<T>(): Record<string, T> {
|
||||||
const data: Record<string, unknown> = {};
|
const data: Record<string, T> = {};
|
||||||
for (let i = 0; i < window.localStorage.length; ++i) {
|
for (let i = 0; i < window.localStorage.length; ++i) {
|
||||||
const key = window.localStorage.key(i);
|
const key = window.localStorage.key(i);
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
@@ -52,5 +56,5 @@ function has(predicate: (key: string) => boolean) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Config = { set, get, clear, all, has };
|
const Config = { set, get, clear, all, has, remove };
|
||||||
export default Config;
|
export default Config;
|
||||||
|
|||||||
Reference in New Issue
Block a user