Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading