Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3525927
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/web/database/utils/constants.js b/web/database/utils/constants.js
index e9fd32736..2875f601b 100644
--- a/web/database/utils/constants.js
+++ b/web/database/utils/constants.js
@@ -1,25 +1,23 @@
// @flow
export const SQLITE_CONTENT = 'sqliteFileContent';
export const SQLITE_ENCRYPTION_KEY = 'encryptionKey';
export const CURRENT_USER_ID_KEY = 'current_user_id';
-export const DB_PERSIST_THROTTLE_WAIT_MS = 300;
-
export const DATABASE_WORKER_PATH = '/worker/database';
export const SQLJS_FILE_PATH = '/compiled/webworkers';
export const DB_SUPPORTED_OS: $ReadOnlyArray<string> = [
'Windows 10',
'Linux',
'Mac OS',
];
export const DB_SUPPORTED_BROWSERS: $ReadOnlyArray<string> = [
'edge',
'edge-chromium',
'chrome',
'firefox',
'opera',
];
diff --git a/web/database/worker/db-worker.js b/web/database/worker/db-worker.js
index 702af9a17..70cd25dbd 100644
--- a/web/database/worker/db-worker.js
+++ b/web/database/worker/db-worker.js
@@ -1,267 +1,277 @@
// @flow
import localforage from 'localforage';
-import _throttle from 'lodash/throttle.js';
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,
workerWriteRequests,
} from '../../types/worker-types.js';
import { getSQLiteDBVersion, setupSQLiteDB } from '../queries/db-queries.js';
import {
getAllDrafts,
moveDraft,
removeAllDrafts,
updateDraft,
} from '../queries/draft-queries.js';
import { getMetadata, setMetadata } from '../queries/metadata-queries.js';
import {
getPersistStorageItem,
removePersistStorageItem,
setPersistStorageItem,
} from '../queries/storage-engine-queries.js';
import {
CURRENT_USER_ID_KEY,
- DB_PERSIST_THROTTLE_WAIT_MS,
SQLITE_CONTENT,
SQLITE_ENCRYPTION_KEY,
} from '../utils/constants.js';
import {
decryptDatabaseFile,
encryptDatabaseFile,
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;
let encryptionKey: ?CryptoKey = null;
+let persistNeeded: boolean = false;
+let persistInProgress: boolean = false;
+
async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) {
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 => {
if (sqljsFilename) {
return `${sqljsFilePath}/${sqljsFilename}`;
}
return `${sqljsFilePath}/${defaultFilename}`;
};
const SQL = await initSqlJs({
locateFile,
});
if (dbContent) {
sqliteDb = new SQL.Database(dbContent);
console.info(
'Database exists and is properly encrypted, using persisted data',
);
} else {
sqliteDb = new SQL.Database();
setupSQLiteDB(sqliteDb);
console.info('Creating fresh database');
}
const dbVersion = getSQLiteDBVersion(sqliteDb);
console.info(`Db version: ${dbVersion}`);
}
function processDraftStoreOperations(
operations: $ReadOnlyArray<ClientDBDraftStoreOperation>,
) {
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: [],
messageStoreThreads: [],
};
}
async function persist() {
+ persistInProgress = true;
if (!sqliteDb) {
+ persistInProgress = false;
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');
+ while (persistNeeded) {
+ persistNeeded = false;
+ const dbData = sqliteDb.export();
+ if (!encryptionKey) {
+ persistInProgress = false;
+ throw new Error('Encryption key is missing');
+ }
+ const encryptedData = await encryptDatabaseFile(dbData, encryptionKey);
+ await localforage.setItem(SQLITE_CONTENT, encryptedData);
}
- const encryptedData = await encryptDatabaseFile(dbData, encryptionKey);
- await localforage.setItem(SQLITE_CONTENT, encryptedData);
+ persistInProgress = false;
}
-const throttledPersist = _throttle(persist, DB_PERSIST_THROTTLE_WAIT_MS);
-
async function processAppRequest(
message: WorkerRequestMessage,
): Promise<?WorkerResponseMessage> {
// non-database operations
if (message.type === workerRequestMessageTypes.PING) {
return {
type: workerResponseMessageTypes.PONG,
text: 'PONG',
};
} else if (
message.type === workerRequestMessageTypes.GENERATE_DATABASE_ENCRYPTION_KEY
) {
const cryptoKey = await generateDatabaseCryptoKey();
await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey);
return undefined;
}
// database operations
if (message.type === workerRequestMessageTypes.INIT) {
await initDatabase(message.sqljsFilePath, message.sqljsFilename);
return undefined;
} else if (message.type === workerRequestMessageTypes.CLEAR_SENSITIVE_DATA) {
encryptionKey = null;
if (sqliteDb) {
sqliteDb.close();
}
await localforage.clear();
return undefined;
}
if (!sqliteDb) {
throw new Error('Database not initialized');
}
// read-only operations
if (message.type === workerRequestMessageTypes.GET_CLIENT_STORE) {
return {
type: workerResponseMessageTypes.CLIENT_STORE,
store: getClientStore(),
};
} else if (message.type === workerRequestMessageTypes.GET_CURRENT_USER_ID) {
return {
type: workerResponseMessageTypes.GET_CURRENT_USER_ID,
userID: getMetadata(sqliteDb, CURRENT_USER_ID_KEY),
};
} else if (
message.type === workerRequestMessageTypes.GET_PERSIST_STORAGE_ITEM
) {
return {
type: workerResponseMessageTypes.GET_PERSIST_STORAGE_ITEM,
item: getPersistStorageItem(sqliteDb, message.key),
};
}
// write operations
if (!workerWriteRequests.includes(message.type)) {
throw new Error('Request type not supported');
}
if (message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS) {
const { draftStoreOperations } = message.storeOperations;
if (draftStoreOperations) {
processDraftStoreOperations(draftStoreOperations);
}
} else if (message.type === workerRequestMessageTypes.SET_CURRENT_USER_ID) {
setMetadata(sqliteDb, CURRENT_USER_ID_KEY, message.userID);
} else if (
message.type === workerRequestMessageTypes.SET_PERSIST_STORAGE_ITEM
) {
setPersistStorageItem(sqliteDb, message.key, message.item);
} else if (
message.type === workerRequestMessageTypes.REMOVE_PERSIST_STORAGE_ITEM
) {
removePersistStorageItem(sqliteDb, message.key);
}
- throttledPersist();
+ persistNeeded = true;
+ if (!persistInProgress) {
+ persist();
+ }
+
return undefined;
}
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);
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Dec 25, 6:49 PM (18 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2700810
Default Alt Text
(9 KB)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment