Changeset View
Changeset View
Standalone View
Standalone View
web/database/worker/db-worker.js
// @flow | // @flow | ||||
import localforage from 'localforage'; | import localforage from 'localforage'; | ||||
import _throttle from 'lodash/throttle.js'; | |||||
import initSqlJs, { type SqliteDatabase } from 'sql.js'; | import initSqlJs, { type SqliteDatabase } from 'sql.js'; | ||||
import type { | import type { | ||||
ClientDBDraftStoreOperation, | ClientDBDraftStoreOperation, | ||||
DraftStoreOperation, | DraftStoreOperation, | ||||
} from 'lib/types/draft-types.js'; | } from 'lib/types/draft-types.js'; | ||||
import type { ClientDBStore } from 'lib/types/store-ops-types.js'; | import type { ClientDBStore } from 'lib/types/store-ops-types.js'; | ||||
Show All 16 Lines | |||||
import { getMetadata, setMetadata } from '../queries/metadata-queries.js'; | import { getMetadata, setMetadata } from '../queries/metadata-queries.js'; | ||||
import { | import { | ||||
getPersistStorageItem, | getPersistStorageItem, | ||||
removePersistStorageItem, | removePersistStorageItem, | ||||
setPersistStorageItem, | setPersistStorageItem, | ||||
} from '../queries/storage-engine-queries.js'; | } from '../queries/storage-engine-queries.js'; | ||||
import { | import { | ||||
CURRENT_USER_ID_KEY, | CURRENT_USER_ID_KEY, | ||||
DB_PERSIST_THROTTLE_WAIT_MS, | |||||
SQLITE_CONTENT, | SQLITE_CONTENT, | ||||
SQLITE_ENCRYPTION_KEY, | SQLITE_ENCRYPTION_KEY, | ||||
} from '../utils/constants.js'; | } from '../utils/constants.js'; | ||||
import { | import { | ||||
decryptDatabaseFile, | decryptDatabaseFile, | ||||
encryptDatabaseFile, | encryptDatabaseFile, | ||||
generateDatabaseCryptoKey, | generateDatabaseCryptoKey, | ||||
} from '../utils/worker-crypto-utils.js'; | } from '../utils/worker-crypto-utils.js'; | ||||
const localforageConfig: PartialConfig = { | const localforageConfig: PartialConfig = { | ||||
driver: localforage.INDEXEDDB, | driver: localforage.INDEXEDDB, | ||||
name: 'comm', | name: 'comm', | ||||
storeName: 'commStorage', | storeName: 'commStorage', | ||||
description: 'Comm encrypted database storage', | description: 'Comm encrypted database storage', | ||||
version: '1.0', | version: '1.0', | ||||
}; | }; | ||||
localforage.config(localforageConfig); | localforage.config(localforageConfig); | ||||
let sqliteDb: ?SqliteDatabase = null; | let sqliteDb: ?SqliteDatabase = null; | ||||
let encryptionKey: ?CryptoKey = null; | let encryptionKey: ?CryptoKey = null; | ||||
let persistNeeded: boolean = false; | |||||
let persistInProgress: boolean = false; | |||||
async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) { | async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) { | ||||
encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | ||||
if (!encryptionKey) { | if (!encryptionKey) { | ||||
const cryptoKey = await generateDatabaseCryptoKey(); | const cryptoKey = await generateDatabaseCryptoKey(); | ||||
await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey); | await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey); | ||||
} | } | ||||
const encryptedContent = await localforage.getItem(SQLITE_CONTENT); | const encryptedContent = await localforage.getItem(SQLITE_CONTENT); | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | return { | ||||
drafts: getAllDrafts(sqliteDb), | drafts: getAllDrafts(sqliteDb), | ||||
messages: [], | messages: [], | ||||
threads: [], | threads: [], | ||||
messageStoreThreads: [], | messageStoreThreads: [], | ||||
}; | }; | ||||
} | } | ||||
async function persist() { | async function persist() { | ||||
persistInProgress = true; | |||||
if (!sqliteDb) { | if (!sqliteDb) { | ||||
throw new Error('Database not initialized'); | throw new Error('Database not initialized'); | ||||
} | } | ||||
if (!encryptionKey) { | if (!encryptionKey) { | ||||
encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | ||||
} | } | ||||
while (true) { | |||||
const dbData = sqliteDb.export(); | const dbData = sqliteDb.export(); | ||||
if (!encryptionKey) { | if (!encryptionKey) { | ||||
throw new Error('Encryption key is missing'); | throw new Error('Encryption key is missing'); | ||||
} | } | ||||
const encryptedData = await encryptDatabaseFile(dbData, encryptionKey); | const encryptedData = await encryptDatabaseFile(dbData, encryptionKey); | ||||
await localforage.setItem(SQLITE_CONTENT, encryptedData); | await localforage.setItem(SQLITE_CONTENT, encryptedData); | ||||
} | |||||
const throttledPersist = _throttle(persist, DB_PERSIST_THROTTLE_WAIT_MS); | if (persistNeeded) { | ||||
persistNeeded = false; | |||||
} else { | |||||
persistInProgress = false; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
async function processAppRequest( | async function processAppRequest( | ||||
message: WorkerRequestMessage, | message: WorkerRequestMessage, | ||||
): Promise<?WorkerResponseMessage> { | ): Promise<?WorkerResponseMessage> { | ||||
// non-database operations | // non-database operations | ||||
if (message.type === workerRequestMessageTypes.PING) { | if (message.type === workerRequestMessageTypes.PING) { | ||||
return { | return { | ||||
type: workerResponseMessageTypes.PONG, | type: workerResponseMessageTypes.PONG, | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | ): Promise<?WorkerResponseMessage> { | ||||
) { | ) { | ||||
setPersistStorageItem(sqliteDb, message.key, message.item); | setPersistStorageItem(sqliteDb, message.key, message.item); | ||||
} else if ( | } else if ( | ||||
message.type === workerRequestMessageTypes.REMOVE_PERSIST_STORAGE_ITEM | message.type === workerRequestMessageTypes.REMOVE_PERSIST_STORAGE_ITEM | ||||
) { | ) { | ||||
removePersistStorageItem(sqliteDb, message.key); | removePersistStorageItem(sqliteDb, message.key); | ||||
} | } | ||||
throttledPersist(); | if (persistInProgress) { | ||||
// persist process is running, it will need to be scheduled again | |||||
persistNeeded = true; | |||||
} else { | |||||
// persist process is not running, scheduling for the first time | |||||
persist(); | |||||
} | |||||
return undefined; | return undefined; | ||||
} | } | ||||
function connectHandler(event: SharedWorkerMessageEvent) { | function connectHandler(event: SharedWorkerMessageEvent) { | ||||
if (!event.ports.length) { | if (!event.ports.length) { | ||||
return; | return; | ||||
} | } | ||||
const port: MessagePort = event.ports[0]; | const port: MessagePort = event.ports[0]; | ||||
Show All 28 Lines |