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 15 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 { generateDatabaseCryptoKey } from '../utils/worker-crypto-utils.js'; | import { | ||||
decryptDatabaseFile, | |||||
encryptDatabaseFile, | |||||
generateDatabaseCryptoKey, | |||||
} 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; | |||||
async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) { | async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) { | ||||
const content = await localforage.getItem(SQLITE_CONTENT); | encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | ||||
if (!encryptionKey) { | |||||
const cryptoKey = await generateDatabaseCryptoKey(); | |||||
await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey); | |||||
} | |||||
const encryptedContent = await localforage.getItem(SQLITE_CONTENT); | |||||
let dbContent = null; | |||||
try { | |||||
if (encryptionKey && encryptedContent) { | |||||
dbContent = await decryptDatabaseFile(encryptedContent, encryptionKey); | |||||
} | |||||
} catch (e) { | |||||
console.error('Error while decrypting content, clearing database content'); | |||||
await localforage.removeItem(SQLITE_CONTENT); | |||||
} | |||||
const locateFile = defaultFilename => { | const locateFile = defaultFilename => { | ||||
if (sqljsFilename) { | if (sqljsFilename) { | ||||
return `${sqljsFilePath}/${sqljsFilename}`; | return `${sqljsFilePath}/${sqljsFilename}`; | ||||
} | } | ||||
return `${sqljsFilePath}/${defaultFilename}`; | return `${sqljsFilePath}/${defaultFilename}`; | ||||
}; | }; | ||||
const SQL = await initSqlJs({ | const SQL = await initSqlJs({ | ||||
locateFile, | locateFile, | ||||
}); | }); | ||||
if (content) { | if (dbContent) { | ||||
sqliteDb = new SQL.Database(new Uint8Array(content)); | sqliteDb = new SQL.Database(dbContent); | ||||
console.info( | |||||
'Database exists and is properly encrypted, using persisted data', | |||||
); | |||||
} else { | } else { | ||||
sqliteDb = new SQL.Database(); | sqliteDb = new SQL.Database(); | ||||
setupSQLiteDB(sqliteDb); | setupSQLiteDB(sqliteDb); | ||||
console.info('Creating fresh database'); | |||||
} | } | ||||
const dbVersion = getSQLiteDBVersion(sqliteDb); | const dbVersion = getSQLiteDBVersion(sqliteDb); | ||||
console.info(`Db version: ${dbVersion}`); | console.info(`Db version: ${dbVersion}`); | ||||
} | } | ||||
function processDraftStoreOperations( | function processDraftStoreOperations( | ||||
operations: $ReadOnlyArray<ClientDBDraftStoreOperation>, | operations: $ReadOnlyArray<ClientDBDraftStoreOperation>, | ||||
Show All 22 Lines | function getClientStore(): ClientDBStore { | ||||
} | } | ||||
return { | return { | ||||
drafts: getAllDrafts(sqliteDb), | drafts: getAllDrafts(sqliteDb), | ||||
messages: [], | messages: [], | ||||
threads: [], | threads: [], | ||||
}; | }; | ||||
} | } | ||||
async function persist() { | |||||
if (!sqliteDb) { | |||||
throw new Error('Database not initialized'); | |||||
} | |||||
if (!encryptionKey) { | |||||
encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | |||||
} | |||||
const dbData = sqliteDb.export(); | |||||
if (!encryptionKey) { | |||||
throw new Error('Encryption key is missing'); | |||||
} | |||||
const encryptedData = await encryptDatabaseFile(dbData, encryptionKey); | |||||
await localforage.setItem(SQLITE_CONTENT, encryptedData); | |||||
} | |||||
const throttledPersist = _throttle(persist, DB_PERSIST_THROTTLE_WAIT_MS); | |||||
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, | ||||
text: 'PONG', | text: 'PONG', | ||||
Show All 37 Lines | ): Promise<?WorkerResponseMessage> { | ||||
} | } | ||||
// write operations | // write operations | ||||
if (message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS) { | if (message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS) { | ||||
const { draftStoreOperations } = message.storeOperations; | const { draftStoreOperations } = message.storeOperations; | ||||
if (draftStoreOperations) { | if (draftStoreOperations) { | ||||
processDraftStoreOperations(draftStoreOperations); | processDraftStoreOperations(draftStoreOperations); | ||||
} | } | ||||
throttledPersist(); | |||||
return; | return; | ||||
} else if (message.type === workerRequestMessageTypes.SET_CURRENT_USER_ID) { | } else if (message.type === workerRequestMessageTypes.SET_CURRENT_USER_ID) { | ||||
setMetadata(sqliteDb, CURRENT_USER_ID_KEY, message.userID); | setMetadata(sqliteDb, CURRENT_USER_ID_KEY, message.userID); | ||||
throttledPersist(); | |||||
return; | return; | ||||
} else if ( | } else if ( | ||||
message.type === workerRequestMessageTypes.SET_PERSIST_STORAGE_ITEM | message.type === workerRequestMessageTypes.SET_PERSIST_STORAGE_ITEM | ||||
) { | ) { | ||||
setPersistStorageItem(sqliteDb, message.key, message.item); | setPersistStorageItem(sqliteDb, message.key, message.item); | ||||
throttledPersist(); | |||||
return; | return; | ||||
} 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(); | |||||
return; | return; | ||||
} | } | ||||
throw new Error('Request type not supported'); | throw new Error('Request type not supported'); | ||||
} | } | ||||
function connectHandler(event: SharedWorkerMessageEvent) { | function connectHandler(event: SharedWorkerMessageEvent) { | ||||
if (!event.ports.length) { | if (!event.ports.length) { | ||||
Show All 31 Lines |