-
-
Save MichalGniadek/edafc0e8f8eeb730070d34f8b0492615 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import localforage from 'localforage'; | |
import { | |
DATABASE_WORKER_PATH, | |
DATABASE_MODULE_FILE_PATH, | |
SQLITE_ENCRYPTION_KEY, | |
} from './utils/constants.js'; | |
import { isDesktopSafari, isSQLiteSupported } from './utils/db-utils.js'; | |
import { | |
exportKeyToJWK, | |
generateDatabaseCryptoKey, | |
} from './utils/worker-crypto-utils.js'; | |
import WorkerConnectionProxy from './utils/WorkerConnectionProxy.js'; | |
import type { AppState } from '../redux/redux-setup.js'; | |
import { | |
workerRequestMessageTypes, | |
type WorkerRequestMessage, | |
type WorkerResponseMessage, | |
} from '../types/worker-types.js'; | |
declare var commQueryExecutorFilename: string; | |
declare var preloadedState: AppState; | |
type InitializedDatabaseState = | |
| { type: 'init_error' } | |
| { | |
type: 'init_success', | |
worker: SharedWorker, | |
workerProxy: WorkerConnectionProxy, | |
}; | |
type DatabaseState = | |
| { type: 'not_supported' } | |
| { type: 'init_in_progress', initPromise: Promise<InitializedDatabaseState> } | |
| InitializedDatabaseState; | |
class DatabaseModule { | |
state: DatabaseState = { type: 'not_supported' }; | |
async init(currentLoggedInUserID: ?string): Promise<void> { | |
if (!currentLoggedInUserID) { | |
return; | |
} | |
if (!isSQLiteSupported(currentLoggedInUserID)) { | |
console.warn('Sqlite is not supported'); | |
this.state = { type: 'not_supported' }; | |
return; | |
} | |
if (this.state.type === 'init_in_progress') { | |
await this.state.initPromise; | |
return; | |
} | |
if ( | |
this.state.type === 'init_success' || | |
this.state.type === 'init_error' | |
) { | |
return; | |
} | |
this.state = { | |
type: 'init_in_progress', | |
initPromise: (async () => { | |
let encryptionKey = null; | |
if (isDesktopSafari) { | |
encryptionKey = await getSafariEncryptionKey(); | |
} | |
const worker = new SharedWorker(DATABASE_WORKER_PATH); | |
worker.onerror = console.error; | |
const workerProxy = new WorkerConnectionProxy( | |
worker.port, | |
console.error, | |
); | |
const origin = window.location.origin; | |
try { | |
await workerProxy.scheduleOnWorker({ | |
type: workerRequestMessageTypes.INIT, | |
databaseModuleFilePath: `${origin}${DATABASE_MODULE_FILE_PATH}`, | |
encryptionKey, | |
commQueryExecutorFilename, | |
}); | |
console.info('Database initialization success'); | |
return { type: 'init_success', worker, workerProxy }; | |
} catch (error) { | |
console.error(`Database initialization failure`, error); | |
return { type: 'init_error' }; | |
} | |
})(), | |
}; | |
this.state = await this.state.initPromise; | |
} | |
async clearSensitiveData(): Promise<void> { | |
if (this.state.type === 'init_success') { | |
await this.state.workerProxy.scheduleOnWorker({ | |
type: workerRequestMessageTypes.CLEAR_SENSITIVE_DATA, | |
}); | |
this.state = { type: 'not_supported' }; | |
} | |
} | |
async isDatabaseSupported(): Promise<boolean> { | |
if (this.state.type === 'init_in_progress') { | |
this.state = await this.state.initPromise; | |
} | |
return this.state.type === 'init_success'; | |
} | |
async schedule( | |
payload: WorkerRequestMessage, | |
): Promise<?WorkerResponseMessage> { | |
if (this.state.type === 'init_in_progress') { | |
this.state = await this.state.initPromise; | |
} | |
if (this.state.type === 'not_supported') { | |
throw new Error('Database not supported'); | |
} | |
if (this.state.type === 'init_error') { | |
throw new Error('Database could not be initialized'); | |
} | |
return this.state.workerProxy.scheduleOnWorker(payload); | |
} | |
} | |
async function getSafariEncryptionKey(): Promise<SubtleCrypto$JsonWebKey> { | |
const encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY); | |
if (encryptionKey) { | |
return await exportKeyToJWK(encryptionKey); | |
} | |
const newEncryptionKey = await generateDatabaseCryptoKey({ | |
extractable: true, | |
}); | |
await localforage.setItem(SQLITE_ENCRYPTION_KEY, newEncryptionKey); | |
return await exportKeyToJWK(newEncryptionKey); | |
} | |
let databaseModule: ?DatabaseModule = null; | |
async function getDatabaseModule(): Promise<DatabaseModule> { | |
if (!databaseModule) { | |
databaseModule = new DatabaseModule(); | |
const currentLoggedInUserID = preloadedState.currentUserInfo?.anonymous | |
? undefined | |
: preloadedState.currentUserInfo?.id; | |
await databaseModule.init(currentLoggedInUserID); | |
} | |
return databaseModule; | |
} | |
export { getDatabaseModule }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment