Skip to content

Commit

Permalink
[web] Move getSafariEncryptionKey to initPromise
Browse files Browse the repository at this point in the history
Summary:
[ENG-5670](https://linear.app/comm/issue/ENG-5670)

Webapp on safari didn't correctly initialize the database and so rehydration failed. Because cookies are now stored in redux it also meant that the user was logged out.
The problem was that the async call to `getSafariEncryptionKey` (which only happens on safari) wasn't in `this.initPromise`. That meant we could end up in a situation where `this.status` is `INIT_IN_PROGRESS` but `initPromise` is undefined. This broke logic in `isDatabaseSupported` which assumed that these two variables would be always in sync.

Test Plan:
Added logs to `DatabaseModule.isDatabaseSupported` and `commReduxStorageEngine.getItem` (which tries to get data from sqlite and internally calls `isDatabaseSupported`)
Without changes in this diff:
  - Reloaded the website
  - From logs in `isDatabaseSupported` we can see that
    - `this.status` is `INIT_IN_PROGRESS` which is correct
    - But `this.initPromise` in `undefined`
  - `isDatabaseSupported` call in `getItem` returns false and rehydration fails
  - User is logged out
With changes:
  - Reloaded the website
  - Now `this.initPromise` contains a pending promise
  - `isDatabaseSupported` call in `getItem` returns true
  - User is remains logged in

Reviewers: kamil, ashoat, tomek

Reviewed By: tomek

Subscribers: wyilio

Differential Revision: https://phab.comm.dev/D9739
  • Loading branch information
MichalGniadek committed Nov 7, 2023
1 parent 8485f33 commit c333201
Showing 1 changed file with 28 additions and 28 deletions.
56 changes: 28 additions & 28 deletions web/database/database-module-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,51 +29,45 @@ const databaseStatuses = Object.freeze({
initError: 'INIT_ERROR',
});

type DatabaseStatus = $Values<typeof databaseStatuses>;
type DatabaseStatus =
| { +type: 'NOT_RUNNING' | 'INIT_SUCCESS' | 'INIT_ERROR' }
| { +type: 'INIT_IN_PROGRESS', +initPromise: Promise<void> };

type InitOptions = { +clearDatabase: boolean };

class DatabaseModule {
worker: ?SharedWorker;
workerProxy: ?WorkerConnectionProxy;
initPromise: ?Promise<void>;
status: DatabaseStatus = databaseStatuses.notRunning;
status: DatabaseStatus = { type: databaseStatuses.notRunning };

async init({ clearDatabase }: InitOptions): Promise<void> {
if (!isSQLiteSupported()) {
console.warn('SQLite is not supported');
this.status = databaseStatuses.initError;
this.status = { type: databaseStatuses.initError };
return;
}

if (clearDatabase && this.status === databaseStatuses.initSuccess) {
if (clearDatabase && this.status.type === databaseStatuses.initSuccess) {
console.info('Clearing sensitive data');
invariant(this.workerProxy, 'Worker proxy should exist');
await this.workerProxy.scheduleOnWorker({
type: workerRequestMessageTypes.CLEAR_SENSITIVE_DATA,
});
this.status = databaseStatuses.notRunning;
this.status = { type: databaseStatuses.notRunning };
}

if (this.status === databaseStatuses.initInProgress) {
await this.initPromise;
if (this.status.type === databaseStatuses.initInProgress) {
await this.status.initPromise;
return;
}

if (
this.status === databaseStatuses.initSuccess ||
this.status === databaseStatuses.initError
this.status.type === databaseStatuses.initSuccess ||
this.status.type === databaseStatuses.initError
) {
return;
}

this.status = databaseStatuses.initInProgress;

let encryptionKey = null;
if (isDesktopSafari) {
encryptionKey = await getSafariEncryptionKey();
}

this.worker = new SharedWorker(DATABASE_WORKER_PATH);
this.worker.onerror = console.error;
this.workerProxy = new WorkerConnectionProxy(
Expand All @@ -83,45 +77,51 @@ class DatabaseModule {

const origin = window.location.origin;

this.initPromise = (async () => {
const initPromise = (async () => {
try {
let encryptionKey = null;
if (isDesktopSafari) {
encryptionKey = await getSafariEncryptionKey();
}
invariant(this.workerProxy, 'Worker proxy should exist');
await this.workerProxy.scheduleOnWorker({
type: workerRequestMessageTypes.INIT,
databaseModuleFilePath: `${origin}${baseURL}${DATABASE_MODULE_FILE_PATH}`,
encryptionKey,
commQueryExecutorFilename,
});
this.status = databaseStatuses.initSuccess;
this.status = { type: databaseStatuses.initSuccess };
console.info('Database initialization success');
} catch (error) {
this.status = databaseStatuses.initError;
this.status = { type: databaseStatuses.initError };
console.error(`Database initialization failure`, error);
}
})();

await this.initPromise;
this.status = { type: databaseStatuses.initInProgress, initPromise };

await initPromise;
}

async isDatabaseSupported(): Promise<boolean> {
if (this.status === databaseStatuses.initInProgress) {
await this.initPromise;
if (this.status.type === databaseStatuses.initInProgress) {
await this.status.initPromise;
}
return this.status === databaseStatuses.initSuccess;
return this.status.type === databaseStatuses.initSuccess;
}

async schedule(
payload: WorkerRequestMessage,
): Promise<?WorkerResponseMessage> {
if (this.status === databaseStatuses.notRunning) {
if (this.status.type === databaseStatuses.notRunning) {
throw new Error('Database not running');
}

if (this.status === databaseStatuses.initInProgress) {
await this.initPromise;
if (this.status.type === databaseStatuses.initInProgress) {
await this.status.initPromise;
}

if (this.status === databaseStatuses.initError) {
if (this.status.type === databaseStatuses.initError) {
throw new Error('Database could not be initialized');
}

Expand Down

0 comments on commit c333201

Please sign in to comment.