diff --git a/packages_web/sqflite_common_ffi_web/example/main.dart b/packages_web/sqflite_common_ffi_web/example/main.dart index 21f307bb..586491f2 100644 --- a/packages_web/sqflite_common_ffi_web/example/main.dart +++ b/packages_web/sqflite_common_ffi_web/example/main.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:html' as html; import 'dart:math'; import 'dart:typed_data'; @@ -9,6 +8,7 @@ import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; import 'package:sqflite_common_ffi_web/src/sw/constants.dart'; import 'package:sqflite_common_ffi_web/src/web/load_sqlite_web.dart' show SqfliteFfiWebContextExt; +import 'package:web/web.dart' as web; import 'ui.dart'; @@ -111,8 +111,9 @@ Future incrementNoWebWorker() async { } Future main() async { + // sqliteFfiWebDebugWebWorker = true; initUi(); - // sqliteFfiWebDebugWebWorker = devWarning(true); + write('$_shc running $_debugVersion'); // devWarning(incrementVarInSharedWorker()); // await devWarning(bigInt()); @@ -129,7 +130,7 @@ Future main() async { var _webContextRegisterAndReady = sqfliteFfiWebStartSharedWorker(swOptions); -Future sharedWorkerRegisterAndReady() async => +Future sharedWorkerRegisterAndReady() async => (await _webContextRegisterAndReady).sharedWorker!; Future webContextRegisterAndReady() async => diff --git a/packages_web/sqflite_common_ffi_web/example/sw.dart b/packages_web/sqflite_common_ffi_web/example/sw.dart index 8b50bbb8..71d441a7 100644 --- a/packages_web/sqflite_common_ffi_web/example/sw.dart +++ b/packages_web/sqflite_common_ffi_web/example/sw.dart @@ -1,7 +1,5 @@ -import 'package:sqflite_common_ffi_web/src/debug/debug.dart'; import 'package:sqflite_common_ffi_web/src/sw/shared_worker.dart'; void main(List args) { - sqliteFfiWebDebugWebWorker = true; // devWarning(true); mainSharedWorker(args); } diff --git a/packages_web/sqflite_common_ffi_web/example/ui.dart b/packages_web/sqflite_common_ffi_web/example/ui.dart index 9fb37191..81c5f517 100644 --- a/packages_web/sqflite_common_ffi_web/example/ui.dart +++ b/packages_web/sqflite_common_ffi_web/example/ui.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'dart:html' as html; +import 'package:web/web.dart' as web; var lines = []; var countLineMax = 100; -var _output = html.querySelector('#output')!; -var _input = html.querySelector('#input')!; +var _output = web.document.querySelector('#output')!; +var _input = web.document.querySelector('#input')!; void write(String message) { print(message); lines.add(message); @@ -15,7 +15,7 @@ void write(String message) { } void addButton(String text, FutureOr Function() action) { - _input.append(html.ButtonElement() + _input.append((web.document.createElement('button') as web.HTMLButtonElement) ..innerText = text ..onClick.listen((event) async { await action(); diff --git a/packages_web/sqflite_common_ffi_web/lib/sqflite_ffi_web.dart b/packages_web/sqflite_common_ffi_web/lib/sqflite_ffi_web.dart index c96e6a73..3cf406db 100644 --- a/packages_web/sqflite_common_ffi_web/lib/sqflite_ffi_web.dart +++ b/packages_web/sqflite_common_ffi_web/lib/sqflite_ffi_web.dart @@ -1,4 +1,6 @@ export 'src/database_factory.dart' show createDatabaseFactoryFfiWeb; +/// Debug only +export 'src/debug/debug.dart' show sqliteFfiWebDebugWebWorker; export 'src/sqflite_ffi.dart' show databaseFactoryFfiWeb, databaseFactoryFfiWebNoWebWorker; export 'src/web/load_sqlite.dart' diff --git a/packages_web/sqflite_common_ffi_web/lib/src/database_factory_web.dart b/packages_web/sqflite_common_ffi_web/lib/src/database_factory_web.dart index 352675bb..692e094a 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/database_factory_web.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/database_factory_web.dart @@ -1,8 +1,7 @@ -import 'dart:html' as html; +import 'dart:js_interop'; import 'package:sqflite_common/sqlite_api.dart'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; -import 'package:sqflite_common_ffi_web/src/debug/debug.dart'; import 'package:sqflite_common_ffi_web/src/sqflite_ffi_impl_web.dart' show SqfliteFfiHandlerWeb; import 'package:sqflite_common_ffi_web/src/utils.dart'; @@ -12,6 +11,7 @@ import 'package:sqflite_common_ffi_web/src/web/load_sqlite_web.dart' SqfliteFfiWebWorkerException, defaultSharedWorkerUri; import 'package:synchronized/synchronized.dart'; +import 'package:web/web.dart' as web; import 'import.dart'; @@ -81,12 +81,13 @@ Future ffiMethodCallSendToWebWorker( _log(st); } if (e is SqfliteFfiWebWorkerException) { - html.window.console.error(''' + web.console.error(''' An error occurred while initializing the web worker. This is likely due to a failure to find the worker javascript file at ${context.options.sharedWorkerUri ?? defaultSharedWorkerUri} Please check the documentation at https://github.com/tekartik/sqflite/tree/master/packages_web/sqflite_common_ffi_web#setup-binaries to setup the needed binaries. -'''); +''' + .toJS); } rethrow; } diff --git a/packages_web/sqflite_common_ffi_web/lib/src/database_file_system_web.dart b/packages_web/sqflite_common_ffi_web/lib/src/database_file_system_web.dart index 844ca736..0b03e483 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/database_file_system_web.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/database_file_system_web.dart @@ -1,18 +1,16 @@ import 'dart:async'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:typed_data'; // ignore: implementation_imports import 'package:sqflite_common/src/mixin/platform.dart'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; -import 'package:sqflite_common_ffi_web/src/debug/debug.dart'; -import 'package:sqflite_common_ffi_web/src/web/js_converter.dart'; import 'package:sqflite_common_ffi_web/src/web/load_sqlite_web.dart'; import 'package:sqlite3/wasm.dart'; +import 'package:web/web.dart' as web; import 'import.dart'; - -var _log = print; +import 'web/worker_message_utils.dart'; /// Database file system on sqlite virtual file system. class SqfliteDatabaseFileSystemFfiWeb implements DatabaseFileSystem { @@ -21,6 +19,7 @@ class SqfliteDatabaseFileSystemFfiWeb implements DatabaseFileSystem { /// Database file system on sqlite virtual file system. SqfliteDatabaseFileSystemFfiWeb(this.fs); + @override Future databaseExists(String path) async { var fs = this.fs; @@ -93,12 +92,6 @@ class SqfliteDatabaseFileSystemFfiWeb implements DatabaseFileSystem { } } -bool get _debug => sqliteFfiWebDebugWebWorker; - -/// Worker client log prefix for debug mode. -var workerClientLogPrefix = '/sw_client'; // Log prefix -var _swc = workerClientLogPrefix; // Log prefix - /// Ffi web handler for custom open/delete operation class SqfliteFfiHandlerWeb extends SqfliteFfiHandler with SqfliteFfiHandlerNonImplementedMixin { @@ -174,88 +167,36 @@ class SqfliteFfiHandlerWeb extends SqfliteFfiHandler } } -/// Genereric Post message handler -abstract class RawMessageSender { - var _firstMessage = true; - - /// Post message to implement - void postMessage(Object message, List transfer); - - /// Returns response - Future sendRawMessage(Object message) { - var completer = Completer(); - // This wraps the message posting/response in a promise, which will resolve if the response doesn't - // contain an error, and reject with the error if it does. If you'd prefer, it's possible to call - // controller.postMessage() and set up the onmessage handler independently of a promise, but this is - // a convenient wrapper. - var messageChannel = html.MessageChannel(); - //var receivePort =ReceivePort(); - - if (_debug) { - _log('$_swc sending $message'); - } - messageChannel.port1.onMessage.listen((event) { - if (_debug) { - _log('$_swc recv ${event.data}'); - } - completer.complete(event.data); - }); - // Let's handle initialization error on the first message. - if (_firstMessage) { - _firstMessage = false; - onError.listen((event) { - if (_debug) { - _log('$_swc error ${jsObjectAsMap(event)}'); - } - - if (!completer.isCompleted) { - completer.completeError(SqfliteFfiWebWorkerException()); - } - }); - } - - // This sends the message data as well as transferring messageChannel.port2 to the shared worker. - // The shared worker can then use the transferred port to reply via postMessage(), which - // will in turn trigger the onmessage handler on messageChannel.port1. - // See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage - postMessage(message, [messageChannel.port2]); - return completer.future; - } - - /// Basic error handling, likely at initialization. - Stream get onError; -} - -/// Post message sender to shared worker. -class RawMessageSenderSharedWorker extends RawMessageSender { - final html.SharedWorker _sharedWorker; - - html.MessagePort get _port => _sharedWorker.port as html.MessagePort; - - /// Post message sender to shared worker. - RawMessageSenderSharedWorker(this._sharedWorker); - - @override - void postMessage(Object message, List transfer) { - _port.postMessage(message, transfer); - } - - @override - Stream get onError => _sharedWorker.onError; -} - /// Post message sender to worker. class RawMessageSenderToWorker extends RawMessageSender { - final html.Worker _worker; - - @override - Stream get onError => _worker.onError; + final web.Worker _worker; /// Post message sender to worker. RawMessageSenderToWorker(this._worker); @override - void postMessage(Object message, List transfer) { - _worker.postMessage(message, transfer); + void postMessage(Object message, web.MessagePort responsePort) { + _worker.postMessage( + message.jsify(), messagePortToPortMessageOption(responsePort)); + } + + StreamController? _errorController; + + @override + Stream get onError { + if (_errorController == null) { + var zone = Zone.current; + _errorController = StreamController.broadcast(onListen: () { + _worker.onerror = (web.Event event) { + zone.run(() { + _errorController!.add(event); + }); + }.toJS; + }, onCancel: () { + _errorController = null; + _worker.onerror = null; + }); + } + return _errorController!.stream; } } diff --git a/packages_web/sqflite_common_ffi_web/lib/src/debug/debug.dart b/packages_web/sqflite_common_ffi_web/lib/src/debug/debug.dart index 5e5c1d69..1c218544 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/debug/debug.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/debug/debug.dart @@ -3,4 +3,13 @@ import 'package:sqflite_common_ffi_web/src/import.dart'; /// For testing. -var sqliteFfiWebDebugWebWorker = false; // devWarning(true); +var _sqliteFfiWebDebugWebWorker = false; // devWarning(true); + +/// Testing only. +bool get sqliteFfiWebDebugWebWorker => _sqliteFfiWebDebugWebWorker; + +/// Testing only. +@Deprecated('testing only') +set sqliteFfiWebDebugWebWorker(bool value) { + _sqliteFfiWebDebugWebWorker = value; +} diff --git a/packages_web/sqflite_common_ffi_web/lib/src/platform/platform.dart b/packages_web/sqflite_common_ffi_web/lib/src/platform/platform.dart index b07c561f..f70e6ec1 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/platform/platform.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/platform/platform.dart @@ -1 +1 @@ -export 'platform_io.dart' if (dart.library.js) 'platform_web.dart'; +export 'platform_io.dart' if (dart.library.js_interop) 'platform_web.dart'; diff --git a/packages_web/sqflite_common_ffi_web/lib/src/sqflite_ffi_impl_web.dart b/packages_web/sqflite_common_ffi_web/lib/src/sqflite_ffi_impl_web.dart index 5ff1403b..0cac5244 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/sqflite_ffi_impl_web.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/sqflite_ffi_impl_web.dart @@ -1,23 +1,13 @@ import 'dart:async'; -import 'dart:html' as html; import 'dart:typed_data'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; -import 'package:sqflite_common_ffi_web/src/debug/debug.dart'; -import 'package:sqflite_common_ffi_web/src/web/js_converter.dart'; import 'package:sqflite_common_ffi_web/src/web/load_sqlite_web.dart'; import 'package:sqlite3/wasm.dart'; import 'database_file_system_web.dart'; import 'import.dart'; -var _log = print; -bool get _debug => sqliteFfiWebDebugWebWorker; - -/// Worker client log prefix for debug mode. -var workerClientLogPrefix = '/sw_client'; // Log prefix -var _swc = workerClientLogPrefix; // Log prefix - /// Ffi web handler for custom open/delete operation class SqfliteFfiHandlerWeb extends SqfliteFfiHandler with SqfliteFfiHandlerNonImplementedMixin { @@ -86,89 +76,3 @@ class SqfliteFfiHandlerWeb extends SqfliteFfiHandler // No op } } - -/// Genereric Post message handler -abstract class RawMessageSender { - var _firstMessage = true; - - /// Post message to implement - void postMessage(Object message, List transfer); - - /// Returns response - Future sendRawMessage(Object message) { - var completer = Completer(); - // This wraps the message posting/response in a promise, which will resolve if the response doesn't - // contain an error, and reject with the error if it does. If you'd prefer, it's possible to call - // controller.postMessage() and set up the onmessage handler independently of a promise, but this is - // a convenient wrapper. - var messageChannel = html.MessageChannel(); - //var receivePort =ReceivePort(); - - if (_debug) { - _log('$_swc sending $message'); - } - messageChannel.port1.onMessage.listen((event) { - if (_debug) { - _log('$_swc recv ${event.data}'); - } - completer.complete(event.data); - }); - // Let's handle initialization error on the first message. - if (_firstMessage) { - _firstMessage = false; - onError.listen((event) { - if (_debug) { - _log('$_swc error ${jsObjectAsMap(event)}'); - } - - if (!completer.isCompleted) { - completer.completeError(SqfliteFfiWebWorkerException()); - } - }); - } - - // This sends the message data as well as transferring messageChannel.port2 to the shared worker. - // The shared worker can then use the transferred port to reply via postMessage(), which - // will in turn trigger the onmessage handler on messageChannel.port1. - // See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage - postMessage(message, [messageChannel.port2]); - return completer.future; - } - - /// Basic error handling, likely at initialization. - Stream get onError; -} - -/// Post message sender to shared worker. -class RawMessageSenderSharedWorker extends RawMessageSender { - final html.SharedWorker _sharedWorker; - - html.MessagePort get _port => _sharedWorker.port as html.MessagePort; - - /// Post message sender to shared worker. - RawMessageSenderSharedWorker(this._sharedWorker); - - @override - void postMessage(Object message, List transfer) { - _port.postMessage(message, transfer); - } - - @override - Stream get onError => _sharedWorker.onError; -} - -/// Post message sender to worker. -class RawMessageSenderToWorker extends RawMessageSender { - final html.Worker _worker; - - @override - Stream get onError => _worker.onError; - - /// Post message sender to worker. - RawMessageSenderToWorker(this._worker); - - @override - void postMessage(Object message, List transfer) { - _worker.postMessage(message, transfer); - } -} diff --git a/packages_web/sqflite_common_ffi_web/lib/src/sw/shared_worker.dart b/packages_web/sqflite_common_ffi_web/lib/src/sw/shared_worker.dart index 26580276..1bc0fae5 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/sw/shared_worker.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/sw/shared_worker.dart @@ -1,10 +1,11 @@ -import 'dart:html'; +import 'dart:async'; +import 'dart:js_interop'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; -import 'package:sqflite_common_ffi_web/src/debug/debug.dart'; import 'package:sqflite_common_ffi_web/src/import.dart'; import 'package:sqflite_common_ffi_web/src/sqflite_ffi_impl_web.dart'; // ignore: implementation_imports import 'package:sqflite_common_ffi_web/src/utils.dart'; +import 'package:web/web.dart' as web; import 'constants.dart'; @@ -20,16 +21,16 @@ var _debugVersion = 2; var _shw = '/shw$_debugVersion'; -Future _handleMessageEvent(Event event) async { - var messageEvent = event as MessageEvent; - var rawData = messageEvent.data; - var port = messageEvent.ports.first; +void _handleMessageEvent(web.Event event) async { + var messageEvent = event as web.MessageEvent; + var rawData = messageEvent.data.dartify(); + var port = messageEvent.ports.toDart.first; try { if (rawData is String) { if (_debug) { _log('$_shw receive text message $rawData'); } - port.postMessage(rawData); + port.postMessage(rawData.toJS); } else { if (_debug) { _log('$_shw recv $rawData'); @@ -51,7 +52,7 @@ Future _handleMessageEvent(Event event) async { _log('$_shw $command $key: $value'); port.postMessage({ 'result': {'key': key, 'value': value} - }); + }.jsify()); } else { _log('$_shw $command unknown'); port.postMessage(null); @@ -77,7 +78,11 @@ Future _handleMessageEvent(Event event) async { sqfliteFfiHandler = SqfliteFfiHandlerWeb(_swContext!); } void postResponse(FfiMethodResponse response) { - port.postMessage(response.toDataMap()); + var data = response.toDataMap(); + if (_debug) { + _log('$_shw resp $data ($port)'); + } + port.postMessage(data.jsify()); } try { @@ -108,26 +113,40 @@ void mainSharedWorker(List args) { if (_debug) { _log('$_shw main($_debugVersion)'); } - + var zone = Zone.current; try { - SharedWorkerGlobalScope.instance.onConnect.listen((event) async { - if (_debug) { - _log('$_shw onConnect()'); - } - var port = (event as MessageEvent).ports.first; - port.addEventListener('message', _handleMessageEvent); - }); + final scope = (globalContext as web.SharedWorkerGlobalScope); + + scope.onconnect = (web.Event event) { + zone.run(() { + if (_debug) { + _log('$_shw onConnect()'); + } + var connectEvent = event as web.MessageEvent; + var port = connectEvent.ports.toDart[0]; + + port.onmessage = (web.MessageEvent event) { + zone.run(() { + _handleMessageEvent(event); + }); + }.toJS; + }); + }.toJS; } catch (e) { + final scope = (globalContext as web.DedicatedWorkerGlobalScope); if (_debug) { _log('$_shw not in shared worker, trying basic worker'); } try { - WorkerGlobalScope.instance - .addEventListener('message', _handleMessageEvent); + scope.onmessage = (web.MessageEvent event) { + zone.run(() { + _handleMessageEvent(event); + }); + }.toJS; } catch (e) { if (_debug) { - _log('$_shw not in shared worker'); + _log('$_shw not in shared worker error $e'); } } } diff --git a/packages_web/sqflite_common_ffi_web/lib/src/web/js_converter.dart b/packages_web/sqflite_common_ffi_web/lib/src/web/js_converter.dart deleted file mode 100755 index 8a79b7b5..00000000 --- a/packages_web/sqflite_common_ffi_web/lib/src/web/js_converter.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:js/js_util.dart'; - -import 'js_interop.dart'; - -/// For JsObject of JsArray -dynamic jsObjectAsCollection(dynamic jsObject, {int? depth}) { - if (jsObject is List) { - return jsArrayAsList(jsObject, depth: depth); - } - return jsObjectAsMap(jsObject, depth: depth); -} - -/// Convert a js array to a dart list and each of its inner member -List? jsArrayAsList(List? jsArray, {int? depth}) { - if (jsArray == null) { - return null; - } - var converter = _Converter(); - return converter.jsArrayToList(jsArray, [], depth: depth); -} - -/// -/// Handle element already in jsCollections -/// -Map? jsObjectAsMap(Object? jsObject, {int? depth}) { - if (jsObject == null) { - return null; - } - var converter = _Converter(); - return converter.jsObjectToMap(jsObject, {}, depth: depth); -} - -/// Returns `true` if the [value] is a very basic built-in type - e.g. -/// [null], [num], [bool] or [String]. It returns `false` in the other case. -bool _isBasicType(Object? value) { - if (value == null || value is num || value is bool || value is String) { - return true; - } - return false; -} - -bool _isCollectionType(Object? value) { - if (_isBasicType(value)) { - return false; - } - return true; -} - -/// Fixed in 2020-09-03 -bool jsIsCollection(Object jsObject) { - return _isCollectionType(jsObject); - /* - return jsObject != null && - (jsObject is Iterable || - jsObject is Map || - isJsArray(jsObject) || - isJsObject(jsObject));*/ -} - -/// Check if a js object is a list -bool jsIsList(Object jsObject) { - return jsObject is Iterable; // || isJsArray(jsObject); -} - -class _Converter { - Map jsCollections = {}; - - dynamic jsObjectToCollection(Object jsObject, {int? depth}) { - if (jsCollections.containsKey(jsObject)) { - return jsCollections[jsObject]; - } - - if (jsIsList(jsObject)) { - // create the list before - return jsArrayToList(jsObject as List?, [], depth: depth); - } else { - // create the map before for recursive object - return jsObjectToMap(jsObject, {}, depth: depth); - } - } - - Map jsObjectToMap(Object jsObject, Map map, {int? depth}) { - jsCollections[jsObject] = map; - final keys = jsObjectKeys(jsObject); - - // Stop - if (depth == 0) { - return {'.': '.'}; - } - - // Handle recursive objects - for (var key in keys) { - var value = getProperty(jsObject, key) as Object?; - // devPrint('key $key value ${jsObjectKeys(value)}'); - if (value != null && jsIsCollection(value)) { - // recursive - value = jsObjectToCollection(value, - depth: depth == null ? null : depth - 1); - } - map[key] = value; - } - return map; - } - - List jsArrayToList(List? jsArray, List list, {int? depth}) { - if (depth == 0) { - return ['..']; - } - jsCollections[jsArray] = list; - for (var i = 0; i < jsArray!.length; i++) { - var value = jsArray[i] as Object?; - if (value != null && jsIsCollection(value)) { - value = jsObjectToCollection(value, - depth: depth == null ? null : depth - 1); - } - list.add(value); - } - return list; - } -} diff --git a/packages_web/sqflite_common_ffi_web/lib/src/web/js_interop.dart b/packages_web/sqflite_common_ffi_web/lib/src/web/js_interop.dart deleted file mode 100755 index 3617cba4..00000000 --- a/packages_web/sqflite_common_ffi_web/lib/src/web/js_interop.dart +++ /dev/null @@ -1,8 +0,0 @@ -@JS() -library tekartik_js_utils.src.js_utils.js_interop; - -import 'package:js/js.dart'; - -/// Visible Object keys for a map -@JS('Object.keys') -external List jsObjectKeys(Object obj); diff --git a/packages_web/sqflite_common_ffi_web/lib/src/web/load_sqlite_web.dart b/packages_web/sqflite_common_ffi_web/lib/src/web/load_sqlite_web.dart index 7f95ffc3..e4831b6c 100644 --- a/packages_web/sqflite_common_ffi_web/lib/src/web/load_sqlite_web.dart +++ b/packages_web/sqflite_common_ffi_web/lib/src/web/load_sqlite_web.dart @@ -1,10 +1,11 @@ -import 'dart:html'; +import 'dart:js_interop'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; import 'package:sqflite_common_ffi_web/src/constant.dart'; -import 'package:sqflite_common_ffi_web/src/debug/debug.dart'; -import 'package:sqflite_common_ffi_web/src/sqflite_ffi_impl_web.dart'; import 'package:sqlite3/wasm.dart'; +import 'package:web/web.dart' as web; + +import 'worker_message_utils.dart'; bool get _debug => sqliteFfiWebDebugWebWorker; @@ -14,7 +15,6 @@ var _log = print; /// Load base file system Future sqfliteFfiWebLoadSqlite3FileSystem( SqfliteFfiWebOptions options) async { - // devPrint('options'); var indexedDbName = options.indexedDbName ?? 'sqflite_databases'; final fs = await IndexedDbFileSystem.open(dbName: indexedDbName); return SqfliteFfiWebContextImpl(options: options, fs: fs); @@ -51,15 +51,15 @@ Future sqfliteFfiWebStartSharedWorker( try { var name = 'sqflite_common_ffi_web'; var sharedWorkerUri = options.sharedWorkerUri ?? defaultSharedWorkerUri; - SharedWorker? sharedWorker; - Worker? worker; + web.SharedWorker? sharedWorker; + web.Worker? worker; try { if (!(options.forceAsBasicWorker ?? false)) { if (_debug) { _log( '$_swc registering shared worker $sharedWorkerUri (name: $name)'); } - sharedWorker = SharedWorker(sharedWorkerUri.toString(), name); + sharedWorker = web.SharedWorker(sharedWorkerUri.toString(), name.toJS); } } catch (e) { if (_debug) { @@ -70,7 +70,7 @@ Future sqfliteFfiWebStartSharedWorker( if (_debug) { _log('$_swc registering worker $sharedWorkerUri'); } - worker = Worker(sharedWorkerUri.toString()); + worker = web.Worker(sharedWorkerUri.toString()); } return SqfliteFfiWebContextImpl( options: options, sharedWorker: sharedWorker, worker: worker); @@ -92,10 +92,10 @@ class SqfliteFfiWebContextImpl extends SqfliteFfiWebContext { final WasmSqlite3? wasmSqlite3; /// Optional Client shared worker - final SharedWorker? sharedWorker; + final web.SharedWorker? sharedWorker; /// Optional Client basic worker (if sharedWorker not working) - final Worker? worker; + final web.Worker? worker; /// Raw message sender to either shared worker or basic worker late final RawMessageSender rawMessageSender; @@ -124,10 +124,10 @@ extension SqfliteFfiWebContextExt on SqfliteFfiWebContext { VirtualFileSystem? get fs => _context.fs; /// Shared worker if any - SharedWorker? get sharedWorker => _context.sharedWorker; + web.SharedWorker? get sharedWorker => _context.sharedWorker; /// Web worker if any - SharedWorker? get webWorker => _context.sharedWorker; + web.SharedWorker? get webWorker => _context.sharedWorker; /// Loaded wasm if any WasmSqlite3? get wasmSqlite3 => _context.wasmSqlite3; diff --git a/packages_web/sqflite_common_ffi_web/lib/src/web/worker_message_utils.dart b/packages_web/sqflite_common_ffi_web/lib/src/web/worker_message_utils.dart new file mode 100644 index 00000000..c0563bb6 --- /dev/null +++ b/packages_web/sqflite_common_ffi_web/lib/src/web/worker_message_utils.dart @@ -0,0 +1,146 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; +import 'package:sqflite_common_ffi_web/src/web/load_sqlite_web.dart'; +import 'package:web/web.dart' as web; + +bool get _debug => sqliteFfiWebDebugWebWorker; + +/// Worker client log prefix for debug mode. +var workerClientLogPrefix = '/sw_client'; // Log prefix +var _swc = workerClientLogPrefix; // Log prefix +var _log = print; + +/// Genereric Post message handler +abstract class RawMessageSender { + var _firstMessage = true; + + /// message port parameter + JSObject messagePortToPortMessageOption(web.MessagePort messagePort) { + return [messagePort].toJS; + } + + /// Message to js + JSAny jsifyMessage(Object message) => message.jsify()!; + + /// Post message to implement + void postMessage(Object message, web.MessagePort responsePort); + + /// Returns response + Future sendRawMessage(Object message) { + var completer = Completer(); + // This wraps the message posting/response in a promise, which will resolve if the response doesn't + // contain an error, and reject with the error if it does. If you'd prefer, it's possible to call + // controller.postMessage() and set up the onmessage handler independently of a promise, but this is + // a convenient wrapper. + var messageChannel = web.MessageChannel(); + //var receivePort =ReceivePort(); + + if (_debug) { + _log('$_swc sending $message'); + } + final zone = Zone.current; + messageChannel.port1.onmessage = (web.MessageEvent event) { + zone.run(() { + var data = event.data.dartify(); + if (_debug) { + _log('$_swc recv $data'); + } + completer.complete(data); + }); + }.toJS; + + // Let's handle initialization error on the first message. + if (_firstMessage) { + _firstMessage = false; + onError.listen((event) { + if (_debug) { + _log('$_swc error $event'); + } + + if (!completer.isCompleted) { + completer.completeError(SqfliteFfiWebWorkerException()); + } + }); + } + + // This sends the message data as well as transferring messageChannel.port2 to the shared worker. + // The shared worker can then use the transferred port to reply via postMessage(), which + // will in turn trigger the onmessage handler on messageChannel.port1. + // See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage + postMessage(message, messageChannel.port2); + return completer.future; + } + + /// Basic error handling, likely at initialization. + Stream get onError; +} + +/// Post message sender to shared worker. +class RawMessageSenderSharedWorker extends RawMessageSender { + final web.SharedWorker _sharedWorker; + + web.MessagePort get _port => _sharedWorker.port; + + /// Post message sender to shared worker. + RawMessageSenderSharedWorker(this._sharedWorker); + + @override + void postMessage(Object message, web.MessagePort responsePort) { + _port.postMessage( + message.jsify(), messagePortToPortMessageOption(responsePort)); + } + + StreamController? _errorController; + @override + Stream get onError { + if (_errorController == null) { + var zone = Zone.current; + _errorController = StreamController.broadcast(onListen: () { + _sharedWorker.onerror = (web.Event event) { + zone.run(() { + _errorController!.add(event); + }); + }.toJS; + }, onCancel: () { + _errorController = null; + _sharedWorker.onerror = null; + }); + } + return _errorController!.stream; + } +} + +/// Post message sender to worker. +class RawMessageSenderToWorker extends RawMessageSender { + final web.Worker _worker; + + StreamController? _errorController; + + @override + Stream get onError { + if (_errorController == null) { + var zone = Zone.current; + _errorController = StreamController.broadcast(onListen: () { + _worker.onerror = (web.Event event) { + zone.run(() { + _errorController!.add(event); + }); + }.toJS; + }, onCancel: () { + _errorController = null; + _worker.onerror = null; + }); + } + return _errorController!.stream; + } + + /// Post message sender to worker. + RawMessageSenderToWorker(this._worker); + + @override + void postMessage(Object message, web.MessagePort responsePort) { + _worker.postMessage( + message.jsify(), messagePortToPortMessageOption(responsePort)); + } +} diff --git a/packages_web/sqflite_common_ffi_web/pubspec.yaml b/packages_web/sqflite_common_ffi_web/pubspec.yaml index 8da72c4f..28ebd00e 100644 --- a/packages_web/sqflite_common_ffi_web/pubspec.yaml +++ b/packages_web/sqflite_common_ffi_web/pubspec.yaml @@ -14,7 +14,7 @@ topics: - database environment: - sdk: '>=3.2.0 <4.0.0' + sdk: '>=3.3.0 <4.0.0' dependencies: path: '>=1.7.0 <3.0.0' @@ -22,7 +22,7 @@ dependencies: sqflite_common: '>=2.5.0+2 <4.0.0' sqlite3: '>=2.3.0 <4.0.0' http: '>=0.13.4 <2.0.0' - js: '>=0.6.4 <2.0.0' + web: '>=0.5.0 <2.0.0' synchronized: '>=3.0.0+3 <5.0.0' process_run: '>=0.12.3+2 <2.0.0' dev_build: '>=0.16.3+1 <2.0.0' diff --git a/packages_web/sqflite_common_ffi_web/tool/setup_build_and_serve_example.dart b/packages_web/sqflite_common_ffi_web/tool/setup_build_and_serve_example.dart new file mode 100644 index 00000000..315373f8 --- /dev/null +++ b/packages_web/sqflite_common_ffi_web/tool/setup_build_and_serve_example.dart @@ -0,0 +1,19 @@ +// ignore_for_file: avoid_print + +import 'package:process_run/shell.dart'; +import 'package:sqflite_common_ffi_web/src/setup/setup.dart'; + +import 'setup_example.dart'; + +Future main(List args) async { + if (true) { + await setupExample(); + await run(''' + dart pub get + webdev build -o example:build + '''); + } + await dhttpdReady; + print('http://localhost:8080'); + await Shell(workingDirectory: 'build').run('dhttpd'); +} diff --git a/packages_web/sqflite_common_ffi_web_test/pubspec.yaml b/packages_web/sqflite_common_ffi_web_test/pubspec.yaml index 9307e390..c08249ce 100644 --- a/packages_web/sqflite_common_ffi_web_test/pubspec.yaml +++ b/packages_web/sqflite_common_ffi_web_test/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.1.0 publish_to: none environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.3.0 <4.0.0' dependencies: path: '>=1.7.0' @@ -13,6 +13,7 @@ dependencies: sqflite_common: sqflite_common_test: path: ../../sqflite_common_test + web: '>=0.5.0' dev_dependencies: build_runner: '>=2.1.4' build_web_compilers: '>=3.2.1' diff --git a/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_no_web_worker_test.dart b/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_no_web_worker_test.dart index f5bf9c5e..ca2c588e 100644 --- a/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_no_web_worker_test.dart +++ b/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_no_web_worker_test.dart @@ -13,11 +13,19 @@ class SqfliteFfiWebNoWebWorkerTestContext extends SqfliteLocalTestContext { var ffiTestContext = SqfliteFfiWebNoWebWorkerTestContext(); Future main() async { + /// Tmp debug + // sqliteFfiWebDebugWebWorker = true; + /// Initialize ffi loader //sqfliteFfiInit(); // Add _no_isolate suffix to the path - var dbsPath = await _factory.getDatabasesPath(); - await _factory.setDatabasesPath('${dbsPath}_no_web_worker'); + try { + var dbsPath = await _factory.getDatabasesPath(); + await _factory.setDatabasesPath('${dbsPath}_no_web_worker'); - all.run(ffiTestContext); + all.run(ffiTestContext); + } catch (e) { + print('Please run setup_web_tests first'); + test('Please run setup_web_tests first', () {}, skip: true); + } } diff --git a/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_test.dart b/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_test.dart index a8c16e16..840f3cc2 100644 --- a/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_test.dart +++ b/packages_web/sqflite_common_ffi_web_test/test/sqflite_ffi_web_test.dart @@ -17,9 +17,16 @@ var ffiTestContext = SqfliteFfiWebTestContext(); Future main() async { /// Initialize ffi loader + // sqliteFfiWebDebugWebWorker = true; sqfliteFfiInit(); - var dbsPath = await _factory.getDatabasesPath(); - await _factory.setDatabasesPath('${dbsPath}_web'); + print('1'); + try { + var dbsPath = await _factory.getDatabasesPath(); + await _factory.setDatabasesPath('${dbsPath}_web'); - all.run(ffiTestContext); + all.run(ffiTestContext); + } catch (e) { + print('Please run setup_web_tests first'); + test('Please run setup_web_tests first', () {}, skip: true); + } } diff --git a/packages_web/sqflite_common_ffi_web_test/tool/build_and_serve.dart b/packages_web/sqflite_common_ffi_web_test/tool/build_and_serve.dart new file mode 100644 index 00000000..94a1db8a --- /dev/null +++ b/packages_web/sqflite_common_ffi_web_test/tool/build_and_serve.dart @@ -0,0 +1,15 @@ +// ignore: depend_on_referenced_packages +import 'package:dev_build/build_support.dart'; +import 'package:path/path.dart'; +import 'package:process_run/process_run.dart'; + +Future main() async { + await checkAndActivatePackage('dhttpd'); + await checkAndActivateWebdev(); + var shell = Shell(); + var port = 8080; + await shell.run('dart pub global run webdev build -o web:build/web'); + shell = shell.cd(join('build', 'web')); + print('http://localhost:$port'); + await shell.run('dart pub global run dhttpd . --port $port .'); +} diff --git a/packages_web/sqflite_common_ffi_web_test/web/main.dart b/packages_web/sqflite_common_ffi_web_test/web/main.dart index 7a280353..8b40bc02 100644 --- a/packages_web/sqflite_common_ffi_web_test/web/main.dart +++ b/packages_web/sqflite_common_ffi_web_test/web/main.dart @@ -5,6 +5,7 @@ import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; import 'ui.dart'; Future main() async { + // sqliteFfiWebDebugWebWorker = true; // Use the ffi web factory in web apps (flutter or dart) with an overriden file name for testing var factory = createDatabaseFactoryFfiWeb( options: diff --git a/packages_web/sqflite_common_ffi_web_test/web/ui.dart b/packages_web/sqflite_common_ffi_web_test/web/ui.dart index 9fb37191..81c5f517 100644 --- a/packages_web/sqflite_common_ffi_web_test/web/ui.dart +++ b/packages_web/sqflite_common_ffi_web_test/web/ui.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'dart:html' as html; +import 'package:web/web.dart' as web; var lines = []; var countLineMax = 100; -var _output = html.querySelector('#output')!; -var _input = html.querySelector('#input')!; +var _output = web.document.querySelector('#output')!; +var _input = web.document.querySelector('#input')!; void write(String message) { print(message); lines.add(message); @@ -15,7 +15,7 @@ void write(String message) { } void addButton(String text, FutureOr Function() action) { - _input.append(html.ButtonElement() + _input.append((web.document.createElement('button') as web.HTMLButtonElement) ..innerText = text ..onClick.listen((event) async { await action(); diff --git a/sqflite_test_app/lib/main_io.dart b/sqflite_test_app/lib/main_io.dart index 4e3cb9fa..022825b2 100644 --- a/sqflite_test_app/lib/main_io.dart +++ b/sqflite_test_app/lib/main_io.dart @@ -1,10 +1,12 @@ import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:sqflite_example/main.dart'; import 'main_ffi.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); if (Platform.isWindows || Platform.isLinux) { mainFfi(); return; diff --git a/sqflite_test_app/lib/main_web.dart b/sqflite_test_app/lib/main_web.dart index ee181eb0..3e9f56f1 100644 --- a/sqflite_test_app/lib/main_web.dart +++ b/sqflite_test_app/lib/main_web.dart @@ -1,5 +1,15 @@ +import 'package:flutter/foundation.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; + import 'main_ffi.dart'; void main() { + if (kIsWeb) { + // sqliteFfiWebDebugWebWorker = true; + // ignore: avoid_print + print('running on the web'); + databaseFactory = databaseFactoryFfiWeb; + } mainFfi(); } diff --git a/sqflite_test_app/lib/src/database_factory_web.dart b/sqflite_test_app/lib/src/database_factory_web.dart deleted file mode 100644 index 8f6c5145..00000000 --- a/sqflite_test_app/lib/src/database_factory_web.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:sqflite_common/sqlite_api.dart'; -// ignore: implementation_imports -import 'package:sqflite_common/src/method_call.dart'; -// ignore: implementation_imports -import 'package:sqflite_common/src/mixin/import_mixin.dart'; - -// ignore_for_file: avoid_print - -DatabaseFactory? _databaseFactoryWebImpl; - -/// The Ffi database factory. -DatabaseFactory get databaseFactoryWeb => - _databaseFactoryWebImpl ??= buildDatabaseFactory( - tag: 'web', - invokeMethod: (String method, [Object? arguments]) { - final methodCall = SqfliteMethodCall(method, arguments); - return methodCall.handle(); - }); - -bool _debug = true; - -/// Extension on MethodCall -extension WebMethodCallHandler on SqfliteMethodCall { - /// Handle a method call - Future handle() async { - try { - if (_debug) { - print('main_send: $this'); - } - const result = 'TODO'; - - /// - /// TODO Implement Web support - /// - if (_debug) { - print('main_recv: $result'); - } - return result; - } catch (e, st) { - if (_debug) { - print(e); - print(st); - } - rethrow; - } - } -} diff --git a/sqflite_test_app/lib/src/sqflite_web_sim.dart b/sqflite_test_app/lib/src/sqflite_web_sim.dart index e869c706..6e5a9a37 100644 --- a/sqflite_test_app/lib/src/sqflite_web_sim.dart +++ b/sqflite_test_app/lib/src/sqflite_web_sim.dart @@ -1,7 +1,6 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:sqflite/sqflite_dev.dart'; - -import 'database_factory_web.dart'; +import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; /// The Web plugin registration. /// @@ -12,6 +11,6 @@ class SqflitePluginWeb { /// Set the default database factory to use. /// Currently calling an on-purpose deprecated helper. // ignore: invalid_use_of_visible_for_testing_member - setMockDatabaseFactory(databaseFactoryWeb); + setMockDatabaseFactory(databaseFactoryFfiWeb); } } diff --git a/sqflite_test_app/tool/flutter_build_and_serve.dart b/sqflite_test_app/tool/flutter_build_and_serve.dart new file mode 100644 index 00000000..336198f5 --- /dev/null +++ b/sqflite_test_app/tool/flutter_build_and_serve.dart @@ -0,0 +1,16 @@ +import 'package:dev_build/build_support.dart'; +import 'package:path/path.dart'; +import 'package:process_run/shell.dart'; + +Future main() async { + await checkAndActivatePackage('dhttpd'); + var shell = Shell(); + await shell.run(''' + flutter build web'''); + shell = shell.cd(join('build', 'web')); + // ignore: avoid_print + print('http://localhost:8080'); + + await shell.run( + 'dart pub global run dhttpd:dhttpd . --headers=Cross-Origin-Embedder-Policy=credentialless;Cross-Origin-Opener-Policy=same-origin'); +}