Skip to content

Commit

Permalink
chore: add check for navigator locks (#356)
Browse files Browse the repository at this point in the history
Co-authored-by: DominicGBauer <[email protected]>
  • Loading branch information
DominicGBauer and DominicGBauer authored Nov 11, 2024
1 parent fc2e340 commit 6e58b32
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 141 deletions.
8 changes: 4 additions & 4 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,20 @@
"devDependencies": {
"@journeyapps/wa-sqlite": "^0.4.1",
"@types/uuid": "^9.0.6",
"@vitest/browser": "^1.3.1",
"@vitest/browser": "^2.1.4",
"crypto-browserify": "^3.12.0",
"p-defer": "^4.0.1",
"source-map-loader": "^5.0.0",
"stream-browserify": "^3.0.0",
"terser-webpack-plugin": "^5.3.9",
"typescript": "^5.5.3",
"uuid": "^9.0.1",
"vite": "^5.1.1",
"vite": "^5.4.10",
"vite-plugin-top-level-await": "^1.4.1",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^1.3.1",
"vitest": "^2.1.4",
"vm-browserify": "^1.1.2",
"webdriverio": "^8.32.3",
"webdriverio": "^8.40.6",
"webpack": "^5.90.1",
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/db/PowerSyncDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
WebStreamingSyncImplementation,
WebStreamingSyncImplementationOptions
} from './sync/WebStreamingSyncImplementation';
import { getNavigatorLocks } from '../shared/navigator';

export interface WebPowerSyncFlags extends WebSQLFlags {
/**
Expand Down Expand Up @@ -160,7 +161,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
if (this.resolvedFlags.ssrMode) {
return PowerSyncDatabase.SHARED_MUTEX.runExclusive(cb);
}
return navigator.locks.request(`lock-${this.database.name}`, cb);
return getNavigatorLocks().request(`lock-${this.database.name}`, cb);
}

protected generateSyncStreamImplementation(connector: PowerSyncBackendConnector): StreamingSyncImplementation {
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { DBFunctionsInterface, OpenDB } from '../../../shared/types';
import { _openDB } from '../../../shared/open-db';
import { getWorkerDatabaseOpener, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database';
import { ResolvedWebSQLOpenOptions, resolveWebSQLFlags, WebSQLFlags } from '../web-sql-flags';
import { getNavigatorLocks } from '../../../shared/navigator';

/**
* These flags are the same as {@link WebSQLFlags}.
Expand Down Expand Up @@ -186,7 +187,7 @@ export class WASQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
}

protected acquireLock(callback: () => Promise<any>): Promise<any> {
return navigator.locks.request(`db-lock-${this.options.dbFilename}`, callback);
return getNavigatorLocks().request(`db-lock-${this.options.dbFilename}`, callback);
}

async readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T> {
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/db/sync/WebStreamingSyncImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
LockType
} from '@powersync/common';
import { ResolvedWebSQLOpenOptions, WebSQLFlags } from '../adapters/web-sql-flags';
import { getNavigatorLocks } from '../../shared/navigator';

export interface WebStreamingSyncImplementationOptions extends AbstractStreamingSyncImplementationOptions {
flags?: WebSQLFlags;
Expand Down Expand Up @@ -32,6 +33,6 @@ export class WebStreamingSyncImplementation extends AbstractStreamingSyncImpleme
obtainLock<T>(lockOptions: LockOptions<T>): Promise<T> {
const identifier = `streaming-sync-${lockOptions.type}-${this.webOptions.identifier}`;
lockOptions.type == LockType.SYNC && console.debug('requesting lock for ', identifier);
return navigator.locks.request(identifier, { signal: lockOptions.signal }, lockOptions.callback);
return getNavigatorLocks().request(identifier, { signal: lockOptions.signal }, lockOptions.callback);
}
}
7 changes: 7 additions & 0 deletions packages/web/src/shared/navigator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const getNavigatorLocks = (): LockManager => {
if ('locks' in navigator && navigator.locks) {
return navigator.locks;
}

throw new Error('Navigator locks are not available in an insecure context. Use a secure context such as HTTPS or http://localhost.');
}
3 changes: 2 additions & 1 deletion packages/web/src/worker/db/WASQLiteDB.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '@journeyapps/wa-sqlite';
import * as Comlink from 'comlink';
import { _openDB } from '../../shared/open-db';
import type { DBFunctionsInterface } from '../../shared/types';
import { getNavigatorLocks } from '../../shared/navigator';

/**
* Keeps track of open DB connections and the clients which
Expand All @@ -23,7 +24,7 @@ let nextClientId = 1;

const openDBShared = async (dbFileName: string): Promise<DBFunctionsInterface> => {
// Prevent multiple simultaneous opens from causing race conditions
return navigator.locks.request(OPEN_DB_LOCK, async () => {
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
const clientId = nextClientId++;

if (!DBMap.has(dbFileName)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/web/src/worker/sync/SharedSyncImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { WASQLiteDBAdapter } from '../../db/adapters/wa-sqlite/WASQLiteDBAdapter';
import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider';
import { BroadcastLogger } from './BroadcastLogger';
import { getNavigatorLocks } from '../../shared/navigator';

/**
* Manual message events for shared sync clients
Expand Down Expand Up @@ -165,7 +166,7 @@ export class SharedSyncImplementation
async connect(options?: PowerSyncConnectionOptions) {
await this.waitForReady();
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
return navigator.locks.request('shared-sync-connect', async () => {
return getNavigatorLocks().request('shared-sync-connect', async () => {
this.syncStreamClient = this.generateStreamingImplementation();

this.syncStreamClient.registerListener({
Expand All @@ -181,7 +182,7 @@ export class SharedSyncImplementation
async disconnect() {
await this.waitForReady();
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
return navigator.locks.request('shared-sync-connect', async () => {
return getNavigatorLocks().request('shared-sync-connect', async () => {
await this.syncStreamClient?.disconnect();
await this.syncStreamClient?.dispose();
this.syncStreamClient = null;
Expand Down
2 changes: 1 addition & 1 deletion packages/web/tests/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AbstractPowerSyncDatabase } from '@powersync/common';
import { v4 as uuid } from 'uuid';
import { TestDatabase, generateTestDb } from './utils/testDb';
Expand Down
4 changes: 2 additions & 2 deletions packages/web/tests/performance.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { AbstractPowerSyncDatabase, Schema, TableV2, column } from '@powersync/common';
import { AbstractPowerSyncDatabase, Schema, Table, column } from '@powersync/common';
import { PowerSyncDatabase } from '@powersync/web';

describe('Basic', () => {
const users = new TableV2({
const users = new Table({
name: column.text,
email: column.text
});
Expand Down
27 changes: 27 additions & 0 deletions packages/web/tests/shared/navigator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { getNavigatorLocks } from '../../src/shared/navigator';

describe('getNavigationLocks', () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('should return native navigator.locks if available', () => {
const mockLocks = {
request: vi.fn(),
query: vi.fn(),
};

vi.spyOn(navigator, 'locks', 'get').mockReturnValue(mockLocks);

const result = getNavigatorLocks();
expect(result).toBe(mockLocks);
});

it('should throw an error if navigator.locks is unavailable', () => {

vi.spyOn(navigator, 'locks', 'get').mockReturnValue(undefined!);

expect(() => getNavigatorLocks()).toThrowError('Navigator locks are not available in an insecure context. Use a secure context such as HTTPS or http://localhost.');;
});
});
Loading

0 comments on commit 6e58b32

Please sign in to comment.