diff --git a/drift/lib/src/web/wasm_setup.dart b/drift/lib/src/web/wasm_setup.dart index 72076d95..5174f11e 100644 --- a/drift/lib/src/web/wasm_setup.dart +++ b/drift/lib/src/web/wasm_setup.dart @@ -11,15 +11,21 @@ import 'dart:async'; import 'dart:html'; import 'package:async/async.dart'; +import 'package:drift/remote.dart'; import 'package:drift/wasm.dart'; import 'package:js/js.dart'; import 'package:js/js_util.dart'; +import 'package:sqlite3/wasm.dart'; +import 'channel.dart'; import 'wasm_setup/protocol.dart'; +/// Whether the `crossOriginIsolated` JavaScript property is true in the current +/// context. @JS() external bool get crossOriginIsolated; +/// Whether shared workers can be constructed in the current context. bool get supportsSharedWorkers => hasProperty(globalThis, 'SharedWorker'); Future openWasmDatabase({ @@ -27,6 +33,24 @@ Future openWasmDatabase({ required Uri driftWorkerUri, required String databaseName, }) async { + final missingFeatures = {}; + + Future connect(WasmStorageImplementation impl, + void Function(WasmInitializationMessage) send) async { + final channel = MessageChannel(); + final local = channel.port1.channel(); + final message = ServeDriftDatabase( + sqlite3WasmUri: sqlite3WasmUri, + port: channel.port2, + storage: impl, + databaseName: databaseName, + ); + send(message); + + final connection = await connectToRemoteAndInitialize(local); + return WasmDatabaseResult(connection, impl, missingFeatures); + } + // First, let's see if we can spawn dedicated workers in shared workers, which // would enable us to efficiently share a OPFS database. if (supportsSharedWorkers) { @@ -39,17 +63,47 @@ Future openWasmDatabase({ // First, the shared worker will tell us which features it supports. final sharedFeatures = await sharedMessages.next as SharedWorkerStatus; + + // Can we use the shared OPFS implementation? + if (sharedFeatures.canSpawnDedicatedWorkers && + sharedFeatures.dedicatedWorkersCanUseOpfs) { + return connect( + WasmStorageImplementation.opfsShared, (msg) => msg.sendToPort(port)); + } else { + missingFeatures.addAll(sharedFeatures.missingFeatures); + await sharedMessages.cancel(); + port.close(); + } } else { - // If we don't support shared workers, we might still have support for - // OPFS in dedicated workers. - final dedicatedWorker = Worker(driftWorkerUri.toString()); - DedicatedWorkerCompatibilityCheck().sendToWorker(dedicatedWorker); + missingFeatures.add(MissingBrowserFeature.sharedWorkers); + } - final workerMessages = StreamQueue( - dedicatedWorker.onMessage.map(WasmInitializationMessage.fromJs)); + final dedicatedWorker = Worker(driftWorkerUri.toString()); + DedicatedWorkerCompatibilityCheck().sendToWorker(dedicatedWorker); - final status = - await workerMessages.next as DedicatedWorkerCompatibilityResult; + final workerMessages = StreamQueue( + dedicatedWorker.onMessage.map(WasmInitializationMessage.fromJs)); + + final status = + await workerMessages.next as DedicatedWorkerCompatibilityResult; + missingFeatures.addAll(status.missingFeatures); + + if (status.canAccessOpfs && status.supportsSharedArrayBuffers) { + // todo send second worker to first one + } else if (status.supportsIndexedDb) { + return connect(WasmStorageImplementation.unsafeIndexedDb, + (msg) => msg.sendToWorker(dedicatedWorker)); + } else { + // Nothing works on this browser, so we'll fall back to an in-memory + // database. + final sqlite3 = await WasmSqlite3.loadFromUrl(sqlite3WasmUri); + sqlite3.registerVirtualFileSystem(InMemoryFileSystem()); + + return WasmDatabaseResult( + WasmDatabase(sqlite3: sqlite3, path: '/app.db'), + WasmStorageImplementation.inMemory, + missingFeatures, + ); } throw 'todo'; diff --git a/drift/lib/src/web/wasm_setup/dedicated_worker.dart b/drift/lib/src/web/wasm_setup/dedicated_worker.dart index 10c37b3b..be57f21c 100644 --- a/drift/lib/src/web/wasm_setup/dedicated_worker.dart +++ b/drift/lib/src/web/wasm_setup/dedicated_worker.dart @@ -6,6 +6,7 @@ import 'dart:html'; import 'package:drift/drift.dart'; import 'package:drift/remote.dart'; import 'package:drift/wasm.dart'; +import 'package:js/js_util.dart'; import 'package:sqlite3/wasm.dart'; import '../channel.dart'; @@ -36,6 +37,8 @@ class DedicatedDriftWorker { DedicatedWorkerCompatibilityResult( canAccessOpfs: supportsOpfs, supportsIndexedDb: supportsIndexedDb, + supportsSharedArrayBuffers: + hasProperty(globalThis, 'SharedArrayBuffer'), ).sendToClient(self); case ServeDriftDatabase(): final server = _servers.putIfAbsent(message.databaseName, () { diff --git a/drift/lib/src/web/wasm_setup/protocol.dart b/drift/lib/src/web/wasm_setup/protocol.dart index 7aab4a9f..d295d936 100644 --- a/drift/lib/src/web/wasm_setup/protocol.dart +++ b/drift/lib/src/web/wasm_setup/protocol.dart @@ -62,12 +62,10 @@ final class SharedWorkerStatus extends WasmInitializationMessage { final bool canSpawnDedicatedWorkers; final bool dedicatedWorkersCanUseOpfs; - final bool canUseIndexedDb; SharedWorkerStatus({ required this.canSpawnDedicatedWorkers, required this.dedicatedWorkersCanUseOpfs, - required this.canUseIndexedDb, }); factory SharedWorkerStatus.fromJsPayload(Object payload) { @@ -76,7 +74,6 @@ final class SharedWorkerStatus extends WasmInitializationMessage { return SharedWorkerStatus( canSpawnDedicatedWorkers: data[0], dedicatedWorkersCanUseOpfs: data[1], - canUseIndexedDb: data[2], ); } @@ -85,9 +82,18 @@ final class SharedWorkerStatus extends WasmInitializationMessage { sender.sendTyped(type, [ canSpawnDedicatedWorkers, dedicatedWorkersCanUseOpfs, - canUseIndexedDb ]); } + + Iterable get missingFeatures sync* { + if (!canSpawnDedicatedWorkers) { + yield MissingBrowserFeature.dedicatedWorkersInSharedWorkers; + } + + if (!dedicatedWorkersCanUseOpfs) { + yield MissingBrowserFeature.fileSystemAccess; + } + } } /// A message sent by a worker when an error occurred. @@ -168,16 +174,20 @@ final class DedicatedWorkerCompatibilityResult static const type = 'DedicatedWorkerCompatibilityResult'; final bool canAccessOpfs; + final bool supportsSharedArrayBuffers; final bool supportsIndexedDb; DedicatedWorkerCompatibilityResult({ required this.canAccessOpfs, + required this.supportsSharedArrayBuffers, required this.supportsIndexedDb, }); factory DedicatedWorkerCompatibilityResult.fromJsPayload(Object payload) { return DedicatedWorkerCompatibilityResult( canAccessOpfs: getProperty(payload, 'canAccessOpfs'), + supportsSharedArrayBuffers: + getProperty(payload, 'supportsSharedArrayBuffers'), supportsIndexedDb: getProperty(payload, 'supportsIndexedDb'), ); } @@ -187,9 +197,23 @@ final class DedicatedWorkerCompatibilityResult final object = newObject(); setProperty(object, 'canAccessOpfs', canAccessOpfs); setProperty(object, 'supportsIndexedDb', supportsIndexedDb); + setProperty( + object, 'supportsSharedArrayBuffers', supportsSharedArrayBuffers); sender.sendTyped(type, object); } + + Iterable get missingFeatures sync* { + if (!canAccessOpfs) { + yield MissingBrowserFeature.fileSystemAccess; + } + if (!supportsSharedArrayBuffers) { + yield MissingBrowserFeature.sharedArrayBuffers; + } + if (!supportsIndexedDb) { + yield MissingBrowserFeature.indexedDb; + } + } } final class StartFileSystemServer extends WasmInitializationMessage { diff --git a/drift/lib/src/web/wasm_setup/shared_worker.dart b/drift/lib/src/web/wasm_setup/shared_worker.dart index 80dd32db..3fc6b267 100644 --- a/drift/lib/src/web/wasm_setup/shared_worker.dart +++ b/drift/lib/src/web/wasm_setup/shared_worker.dart @@ -61,13 +61,11 @@ class SharedDriftWorker { Future _startFeatureDetection() async { // First, let's see if this shared worker can spawn dedicated workers. final hasWorker = hasProperty(self, 'Worker'); - final canUseIndexedDb = await checkIndexedDbSupport(); if (!hasWorker) { return SharedWorkerStatus( canSpawnDedicatedWorkers: false, dedicatedWorkersCanUseOpfs: false, - canUseIndexedDb: canUseIndexedDb, ); } else { final worker = _dedicatedWorker = Worker(Uri.base.toString()); @@ -83,7 +81,6 @@ class SharedDriftWorker { completer.complete(SharedWorkerStatus( canSpawnDedicatedWorkers: true, dedicatedWorkersCanUseOpfs: result, - canUseIndexedDb: canUseIndexedDb, )); messageSubscription?.cancel();