web: fix storage for Firefox & Tor private mode

This commit is contained in:
Abdullah Atta
2023-08-05 11:16:01 +05:00
parent 643028f5d8
commit 3c575c01ad
5 changed files with 53 additions and 38 deletions

View File

@@ -42,7 +42,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
});
database.setup(
new NNStorage("Notesnook", persistence),
await NNStorage.createInstance("Notesnook", persistence),
EventSource,
FS,
new Compressor()

View File

@@ -28,8 +28,6 @@ import { initalizeLogger, logger } from "./utils/logger";
import { AuthProps } from "./views/auth";
import { loadDatabase } from "./hooks/use-database";
initalizeLogger();
type Route<TProps = null> = {
component: () => Promise<{
default: TProps extends null
@@ -150,6 +148,7 @@ function isSessionExpired(path: Routes): RouteWithPath<AuthProps> | null {
renderApp();
async function renderApp() {
await initalizeLogger();
const {
path,
route: { component, props }

View File

@@ -174,29 +174,25 @@ export type UseStore = <T>(
export class IndexedDBKVStore implements IKVStore {
store: UseStore;
static isIndexedDBSupported(): boolean {
return "indexedDB" in window;
static async isIndexedDBSupported(): Promise<boolean> {
if (!("indexedDB" in window)) return false;
try {
await promisifyIDBRequest(indexedDB.open("checkIDBSupport"));
return true;
} catch {
console.error("IndexedDB is not supported in this browser.");
return false;
}
}
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);
const dbp = promisifyIDBRequest(request);
return (txMode, callback) =>
dbp.then((db) =>
@@ -213,26 +209,26 @@ export class IndexedDBKVStore implements IKVStore {
callback(this.result);
this.result.continue();
};
return this.promisifyRequest(store.transaction);
return promisifyIDBRequest(store.transaction);
}
get<T>(key: string): Promise<T | undefined> {
return this.store("readonly", (store) =>
this.promisifyRequest(store.get(key))
promisifyIDBRequest(store.get(key))
);
}
set(key: string, value: any): Promise<void> {
return this.store("readwrite", (store) => {
store.put(value, key);
return this.promisifyRequest(store.transaction);
return promisifyIDBRequest(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);
return promisifyIDBRequest(store.transaction);
});
}
@@ -241,7 +237,7 @@ export class IndexedDBKVStore implements IKVStore {
Promise.all(
keys.map(async (key) => [
key,
await this.promisifyRequest(store.get(key))
await promisifyIDBRequest(store.get(key))
])
)
);
@@ -250,21 +246,21 @@ export class IndexedDBKVStore implements IKVStore {
delete(key: string): Promise<void> {
return this.store("readwrite", (store) => {
store.delete(key);
return this.promisifyRequest(store.transaction);
return promisifyIDBRequest(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);
return promisifyIDBRequest(store.transaction);
});
}
clear(): Promise<void> {
return this.store("readwrite", (store) => {
store.clear();
return this.promisifyRequest(store.transaction);
return promisifyIDBRequest(store.transaction);
});
}
@@ -272,7 +268,7 @@ export class IndexedDBKVStore implements IKVStore {
return this.store("readonly", (store) => {
// Fast path for modern browsers
if (store.getAllKeys) {
return this.promisifyRequest(
return promisifyIDBRequest(
store.getAllKeys() as unknown as IDBRequest<KeyType[]>
);
}
@@ -289,7 +285,7 @@ export class IndexedDBKVStore implements IKVStore {
return this.store("readonly", (store) => {
// Fast path for modern browsers
if (store.getAll) {
return this.promisifyRequest(store.getAll() as IDBRequest<T[]>);
return promisifyIDBRequest(store.getAll() as IDBRequest<T[]>);
}
const items: T[] = [];
@@ -308,10 +304,10 @@ export class IndexedDBKVStore implements IKVStore {
// (although, hopefully we'll get a simpler path some day)
if (store.getAll && store.getAllKeys) {
return Promise.all([
this.promisifyRequest(
promisifyIDBRequest(
store.getAllKeys() as unknown as IDBRequest<KeyType[]>
),
this.promisifyRequest(store.getAll() as IDBRequest<ValueType[]>)
promisifyIDBRequest(store.getAll() as IDBRequest<ValueType[]>)
]).then(([keys, values]) => keys.map((key, i) => [key, values[i]]));
}
@@ -325,3 +321,16 @@ export class IndexedDBKVStore implements IKVStore {
});
}
}
function promisifyIDBRequest<T = undefined>(
request: IDBRequest<T> | IDBTransaction
): Promise<T> {
return new Promise<T>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - file size hacks
request.oncomplete = request.onsuccess = () => resolve(request.result);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - file size hacks
request.onabort = request.onerror = () => reject(request.error);
});
}

View File

@@ -31,17 +31,21 @@ export type DatabasePersistence = "memory" | "db";
const APP_SALT = "oVzKtazBo7d8sb7TBvY9jw";
export class NNStorage {
database: IKVStore;
database!: IKVStore;
constructor(name: string, persistence: DatabasePersistence = "db") {
this.database =
static async createInstance(
name: string,
persistence: DatabasePersistence = "db"
) {
const storage = new NNStorage();
storage.database =
persistence === "memory"
? new MemoryKVStore()
: IndexedDBKVStore.isIndexedDBSupported()
: (await IndexedDBKVStore.isIndexedDBSupported())
? new IndexedDBKVStore(name, "keyvaluepairs")
: new LocalStorageKVStore();
return storage;
}
read<T>(key: string): Promise<T | undefined> {
@@ -78,7 +82,7 @@ export class NNStorage {
const keyData = await crypto.exportKey(password, salt);
if (
IndexedDBKVStore.isIndexedDBSupported() &&
(await IndexedDBKVStore.isIndexedDBSupported()) &&
window?.crypto?.subtle &&
keyData.key
) {
@@ -94,7 +98,10 @@ export class NNStorage {
}
async getCryptoKey(name: string): Promise<string | undefined> {
if (IndexedDBKVStore.isIndexedDBSupported() && window?.crypto?.subtle) {
if (
(await IndexedDBKVStore.isIndexedDBSupported()) &&
window?.crypto?.subtle
) {
const pbkdfKey = await this.read<CryptoKey>(name);
const cipheredKey = await this.read<EncryptedKey | string>(`${name}@_k`);
if (typeof cipheredKey === "string") return cipheredKey;

View File

@@ -28,8 +28,8 @@ import { DatabasePersistence, NNStorage } from "../interfaces/storage";
import { zip } from "./zip";
let logger: typeof _logger;
function initalizeLogger(persistence: DatabasePersistence = "db") {
initalize(new NNStorage("Logs", persistence));
async function initalizeLogger(persistence: DatabasePersistence = "db") {
initalize(await NNStorage.createInstance("Logs", persistence));
logger = _logger.scope("notesnook-web");
}