Skip to content

Commit 6e58b32

Browse files
DominicGBauerDominicGBauer
andauthored
chore: add check for navigator locks (#356)
Co-authored-by: DominicGBauer <[email protected]>
1 parent fc2e340 commit 6e58b32

File tree

11 files changed

+380
-141
lines changed

11 files changed

+380
-141
lines changed

packages/web/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,20 @@
6969
"devDependencies": {
7070
"@journeyapps/wa-sqlite": "^0.4.1",
7171
"@types/uuid": "^9.0.6",
72-
"@vitest/browser": "^1.3.1",
72+
"@vitest/browser": "^2.1.4",
7373
"crypto-browserify": "^3.12.0",
7474
"p-defer": "^4.0.1",
7575
"source-map-loader": "^5.0.0",
7676
"stream-browserify": "^3.0.0",
7777
"terser-webpack-plugin": "^5.3.9",
7878
"typescript": "^5.5.3",
7979
"uuid": "^9.0.1",
80-
"vite": "^5.1.1",
80+
"vite": "^5.4.10",
8181
"vite-plugin-top-level-await": "^1.4.1",
8282
"vite-plugin-wasm": "^3.3.0",
83-
"vitest": "^1.3.1",
83+
"vitest": "^2.1.4",
8484
"vm-browserify": "^1.1.2",
85-
"webdriverio": "^8.32.3",
85+
"webdriverio": "^8.40.6",
8686
"webpack": "^5.90.1",
8787
"webpack-cli": "^5.1.4",
8888
"webpack-node-externals": "^3.0.0"

packages/web/src/db/PowerSyncDatabase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
WebStreamingSyncImplementation,
2929
WebStreamingSyncImplementationOptions
3030
} from './sync/WebStreamingSyncImplementation';
31+
import { getNavigatorLocks } from '../shared/navigator';
3132

3233
export interface WebPowerSyncFlags extends WebSQLFlags {
3334
/**
@@ -160,7 +161,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
160161
if (this.resolvedFlags.ssrMode) {
161162
return PowerSyncDatabase.SHARED_MUTEX.runExclusive(cb);
162163
}
163-
return navigator.locks.request(`lock-${this.database.name}`, cb);
164+
return getNavigatorLocks().request(`lock-${this.database.name}`, cb);
164165
}
165166

166167
protected generateSyncStreamImplementation(connector: PowerSyncBackendConnector): StreamingSyncImplementation {

packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type { DBFunctionsInterface, OpenDB } from '../../../shared/types';
1515
import { _openDB } from '../../../shared/open-db';
1616
import { getWorkerDatabaseOpener, resolveWorkerDatabasePortFactory } from '../../../worker/db/open-worker-database';
1717
import { ResolvedWebSQLOpenOptions, resolveWebSQLFlags, WebSQLFlags } from '../web-sql-flags';
18+
import { getNavigatorLocks } from '../../../shared/navigator';
1819

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

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

192193
async readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions | undefined): Promise<T> {

packages/web/src/db/sync/WebStreamingSyncImplementation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
LockType
66
} from '@powersync/common';
77
import { ResolvedWebSQLOpenOptions, WebSQLFlags } from '../adapters/web-sql-flags';
8+
import { getNavigatorLocks } from '../../shared/navigator';
89

910
export interface WebStreamingSyncImplementationOptions extends AbstractStreamingSyncImplementationOptions {
1011
flags?: WebSQLFlags;
@@ -32,6 +33,6 @@ export class WebStreamingSyncImplementation extends AbstractStreamingSyncImpleme
3233
obtainLock<T>(lockOptions: LockOptions<T>): Promise<T> {
3334
const identifier = `streaming-sync-${lockOptions.type}-${this.webOptions.identifier}`;
3435
lockOptions.type == LockType.SYNC && console.debug('requesting lock for ', identifier);
35-
return navigator.locks.request(identifier, { signal: lockOptions.signal }, lockOptions.callback);
36+
return getNavigatorLocks().request(identifier, { signal: lockOptions.signal }, lockOptions.callback);
3637
}
3738
}

packages/web/src/shared/navigator.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const getNavigatorLocks = (): LockManager => {
2+
if ('locks' in navigator && navigator.locks) {
3+
return navigator.locks;
4+
}
5+
6+
throw new Error('Navigator locks are not available in an insecure context. Use a secure context such as HTTPS or http://localhost.');
7+
}

packages/web/src/worker/db/WASQLiteDB.worker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import '@journeyapps/wa-sqlite';
66
import * as Comlink from 'comlink';
77
import { _openDB } from '../../shared/open-db';
88
import type { DBFunctionsInterface } from '../../shared/types';
9+
import { getNavigatorLocks } from '../../shared/navigator';
910

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

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

2930
if (!DBMap.has(dbFileName)) {

packages/web/src/worker/sync/SharedSyncImplementation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { WASQLiteDBAdapter } from '../../db/adapters/wa-sqlite/WASQLiteDBAdapter';
2424
import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider';
2525
import { BroadcastLogger } from './BroadcastLogger';
26+
import { getNavigatorLocks } from '../../shared/navigator';
2627

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

171172
this.syncStreamClient.registerListener({
@@ -181,7 +182,7 @@ export class SharedSyncImplementation
181182
async disconnect() {
182183
await this.waitForReady();
183184
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
184-
return navigator.locks.request('shared-sync-connect', async () => {
185+
return getNavigatorLocks().request('shared-sync-connect', async () => {
185186
await this.syncStreamClient?.disconnect();
186187
await this.syncStreamClient?.dispose();
187188
this.syncStreamClient = null;

packages/web/tests/main.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22
import { AbstractPowerSyncDatabase } from '@powersync/common';
33
import { v4 as uuid } from 'uuid';
44
import { TestDatabase, generateTestDb } from './utils/testDb';

packages/web/tests/performance.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2-
import { AbstractPowerSyncDatabase, Schema, TableV2, column } from '@powersync/common';
2+
import { AbstractPowerSyncDatabase, Schema, Table, column } from '@powersync/common';
33
import { PowerSyncDatabase } from '@powersync/web';
44

55
describe('Basic', () => {
6-
const users = new TableV2({
6+
const users = new Table({
77
name: column.text,
88
email: column.text
99
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, it, expect, vi, afterEach } from 'vitest';
2+
import { getNavigatorLocks } from '../../src/shared/navigator';
3+
4+
describe('getNavigationLocks', () => {
5+
afterEach(() => {
6+
vi.restoreAllMocks();
7+
});
8+
9+
it('should return native navigator.locks if available', () => {
10+
const mockLocks = {
11+
request: vi.fn(),
12+
query: vi.fn(),
13+
};
14+
15+
vi.spyOn(navigator, 'locks', 'get').mockReturnValue(mockLocks);
16+
17+
const result = getNavigatorLocks();
18+
expect(result).toBe(mockLocks);
19+
});
20+
21+
it('should throw an error if navigator.locks is unavailable', () => {
22+
23+
vi.spyOn(navigator, 'locks', 'get').mockReturnValue(undefined!);
24+
25+
expect(() => getNavigatorLocks()).toThrowError('Navigator locks are not available in an insecure context. Use a secure context such as HTTPS or http://localhost.');;
26+
});
27+
});

0 commit comments

Comments
 (0)