diff --git a/lib/types/store-ops-types.js b/lib/types/store-ops-types.js index 46c834d98..50021da94 100644 --- a/lib/types/store-ops-types.js +++ b/lib/types/store-ops-types.js @@ -1,26 +1,35 @@ // @flow import type { DraftStoreOperation, ClientDBDraftStoreOperation, + ClientDBDraftInfo, } from './draft-types.js'; import type { + ClientDBMessageInfo, ClientDBMessageStoreOperation, MessageStoreOperation, } from './message-types.js'; import type { + ClientDBThreadInfo, ClientDBThreadStoreOperation, ThreadStoreOperation, } from './thread-types.js'; export type StoreOperations = { +draftStoreOperations: $ReadOnlyArray, +threadStoreOperations: $ReadOnlyArray, +messageStoreOperations: $ReadOnlyArray, }; export type ClientDBStoreOperations = { +draftStoreOperations?: $ReadOnlyArray, +threadStoreOperations?: $ReadOnlyArray, +messageStoreOperations?: $ReadOnlyArray, }; + +export type ClientDBStore = { + +messages: $ReadOnlyArray, + +drafts: $ReadOnlyArray, + +threads: $ReadOnlyArray, +}; diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js index 7227e1fc0..fe8c223a8 100644 --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -1,82 +1,74 @@ // @flow 'use strict'; import { TurboModuleRegistry } from 'react-native'; import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport.js'; -import type { - ClientDBDraftInfo, - ClientDBDraftStoreOperation, -} from 'lib/types/draft-types.js'; +import type { ClientDBDraftStoreOperation } from 'lib/types/draft-types.js'; import type { ClientDBMessageInfo, ClientDBMessageStoreOperation, } from 'lib/types/message-types.js'; +import type { ClientDBStore } from 'lib/types/store-ops-types'; import type { ClientDBThreadInfo, ClientDBThreadStoreOperation, } from 'lib/types/thread-types.js'; -type ClientDBStore = { - +messages: $ReadOnlyArray, - +drafts: $ReadOnlyArray, - +threads: $ReadOnlyArray, -}; - type ClientPublicKeys = { +primaryIdentityPublicKeys: { +ed25519: string, +curve25519: string, }, +notificationIdentityPublicKeys: { +ed25519: string, +curve25519: string, }, +blobPayload: string, +signature: string, }; export interface Spec extends TurboModule { +getDraft: (key: string) => Promise; +updateDraft: (key: string, text: string) => Promise; +moveDraft: (oldKey: string, newKey: string) => Promise; +getClientDBStore: () => Promise; +removeAllDrafts: () => Promise; +getAllMessagesSync: () => $ReadOnlyArray; +processDraftStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processMessageStoreOperationsSync: ( operations: $ReadOnlyArray, ) => void; +getAllThreadsSync: () => $ReadOnlyArray; +processThreadStoreOperations: ( operations: $ReadOnlyArray, ) => Promise; +processThreadStoreOperationsSync: ( operations: $ReadOnlyArray, ) => void; +initializeCryptoAccount: () => Promise; +getUserPublicKey: () => Promise; +getUserOneTimeKeys: () => Promise; +getCodeVersion: () => number; +terminate: () => void; +setNotifyToken: (token: string) => Promise; +clearNotifyToken: () => Promise; +setCurrentUserID: (userID: string) => Promise; +getCurrentUserID: () => Promise; +setDeviceID: (deviceType: string) => Promise; +getDeviceID: () => Promise; +clearSensitiveData: () => Promise; +checkIfDatabaseNeedsDeletion: () => boolean; +reportDBOperationsFailure: () => void; +generateNonce: () => Promise; } export default (TurboModuleRegistry.getEnforcing( 'CommTurboModule', ): Spec); diff --git a/web/database/worker/db-worker.js b/web/database/worker/db-worker.js index 9554832f5..5afb6d6f4 100644 --- a/web/database/worker/db-worker.js +++ b/web/database/worker/db-worker.js @@ -1,146 +1,164 @@ // @flow import localforage from 'localforage'; import initSqlJs, { type SqliteDatabase } from 'sql.js'; import type { ClientDBDraftStoreOperation, DraftStoreOperation, } from 'lib/types/draft-types.js'; +import type { ClientDBStore } from 'lib/types/store-ops-types.js'; import { type SharedWorkerMessageEvent, type WorkerRequestMessage, type WorkerResponseMessage, workerRequestMessageTypes, workerResponseMessageTypes, type WorkerRequestProxyMessage, } from '../../types/worker-types.js'; import { getSQLiteDBVersion, setupSQLiteDB } from '../queries/db-queries.js'; import { + getAllDrafts, moveDraft, removeAllDrafts, updateDraft, } from '../queries/draft-queries.js'; import { SQLITE_CONTENT, SQLITE_ENCRYPTION_KEY } from '../utils/constants.js'; import { generateDatabaseCryptoKey } from '../utils/worker-crypto-utils.js'; const localforageConfig: PartialConfig = { driver: localforage.INDEXEDDB, name: 'comm', storeName: 'commStorage', description: 'Comm encrypted database storage', version: '1.0', }; localforage.config(localforageConfig); let sqliteDb: ?SqliteDatabase = null; async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) { const content = await localforage.getItem(SQLITE_CONTENT); const locateFile = defaultFilename => { if (sqljsFilename) { return `${sqljsFilePath}/${sqljsFilename}`; } return `${sqljsFilePath}/${defaultFilename}`; }; const SQL = await initSqlJs({ locateFile, }); if (content) { sqliteDb = new SQL.Database(new Uint8Array(content)); } else { sqliteDb = new SQL.Database(); setupSQLiteDB(sqliteDb); } const dbVersion = getSQLiteDBVersion(sqliteDb); console.info(`Db version: ${dbVersion}`); } function processDraftStoreOperations( operations: $ReadOnlyArray, ) { if (!sqliteDb) { throw new Error('Database not initialized'); } for (const operation: DraftStoreOperation of operations) { if (operation.type === 'remove_all') { removeAllDrafts(sqliteDb); } else if (operation.type === 'update') { const { key, text } = operation.payload; updateDraft(sqliteDb, key, text); } else if (operation.type === 'move') { const { oldKey, newKey } = operation.payload; moveDraft(sqliteDb, oldKey, newKey); } else { throw new Error('Unsupported draft operation'); } } } +function getClientStore(): ClientDBStore { + if (!sqliteDb) { + throw new Error('Database not initialized'); + } + return { + drafts: getAllDrafts(sqliteDb), + messages: [], + threads: [], + }; +} + async function processAppRequest( message: WorkerRequestMessage, ): Promise { if (message.type === workerRequestMessageTypes.PING) { return { type: workerResponseMessageTypes.PONG, text: 'PONG', }; } else if (message.type === workerRequestMessageTypes.INIT) { await initDatabase(message.sqljsFilePath, message.sqljsFilename); return; } else if ( message.type === workerRequestMessageTypes.GENERATE_DATABASE_ENCRYPTION_KEY ) { const cryptoKey = await generateDatabaseCryptoKey(); await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey); return; } else if ( message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS ) { const { draftStoreOperations } = message.storeOperations; if (draftStoreOperations) { processDraftStoreOperations(draftStoreOperations); } return; + } else if (message.type === workerRequestMessageTypes.GET_CLIENT_STORE) { + return { + type: workerResponseMessageTypes.CLIENT_STORE, + store: getClientStore(), + }; } throw new Error('Request type not supported'); } function connectHandler(event: SharedWorkerMessageEvent) { if (!event.ports.length) { return; } const port: MessagePort = event.ports[0]; console.log('Web database worker alive!'); port.onmessage = async function (messageEvent: MessageEvent) { const data: WorkerRequestProxyMessage = (messageEvent.data: any); const { id, message } = data; if (!id) { port.postMessage({ error: new Error('Request without identifier'), }); } try { const result = await processAppRequest(message); port.postMessage({ id, message: result, }); } catch (e) { port.postMessage({ id, error: e, }); } }; } self.addEventListener('connect', connectHandler); diff --git a/web/types/worker-types.js b/web/types/worker-types.js index 13a07f444..27d465dce 100644 --- a/web/types/worker-types.js +++ b/web/types/worker-types.js @@ -1,66 +1,83 @@ // @flow -import type { ClientDBStoreOperations } from 'lib/types/store-ops-types.js'; +import type { + ClientDBStore, + ClientDBStoreOperations, +} from 'lib/types/store-ops-types.js'; // The types of messages sent from app to worker export const workerRequestMessageTypes = Object.freeze({ PING: 0, INIT: 1, GENERATE_DATABASE_ENCRYPTION_KEY: 2, PROCESS_STORE_OPERATIONS: 3, + GET_CLIENT_STORE: 4, }); export type PingWorkerRequestMessage = { +type: 0, +text: string, }; export type InitWorkerRequestMessage = { +type: 1, +sqljsFilePath: string, +sqljsFilename: ?string, }; export type GenerateDatabaseEncryptionKeyRequestMessage = { +type: 2, }; export type ProcessStoreOperationsRequestMessage = { +type: 3, +storeOperations: ClientDBStoreOperations, }; +export type GetClientStoreRequestMessage = { + +type: 4, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage | GenerateDatabaseEncryptionKeyRequestMessage - | ProcessStoreOperationsRequestMessage; + | ProcessStoreOperationsRequestMessage + | GetClientStoreRequestMessage; export type WorkerRequestProxyMessage = { +id: number, +message: WorkerRequestMessage, }; // The types of messages sent from worker to app export const workerResponseMessageTypes = Object.freeze({ PONG: 0, + CLIENT_STORE: 1, }); export type PongWorkerResponseMessage = { +type: 0, +text: string, }; -export type WorkerResponseMessage = PongWorkerResponseMessage; +export type ClientStoreResponseMessage = { + +type: 1, + +store: ClientDBStore, +}; + +export type WorkerResponseMessage = + | PongWorkerResponseMessage + | ClientStoreResponseMessage; export type WorkerResponseProxyMessage = { +id?: number, +message?: WorkerResponseMessage, +error?: Error, }; // SharedWorker types export type SharedWorkerMessageEvent = MessageEvent & { +ports: $ReadOnlyArray, ... };