Merge pull request #2153 from famedly/karthi/web-js-interop-pkgs
refactor: migrate to web and js_interop pkgs
This commit is contained in:
commit
cde303c939
|
|
@ -748,9 +748,10 @@ class KeyVerification {
|
||||||
// no need to request cache, we already have it
|
// no need to request cache, we already have it
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ignore: unawaited_futures
|
unawaited(
|
||||||
encryption.ssss
|
encryption.ssss
|
||||||
.maybeRequestAll(_verifiedDevices.whereType<DeviceKeys>().toList());
|
.maybeRequestAll(_verifiedDevices.whereType<DeviceKeys>().toList()),
|
||||||
|
);
|
||||||
if (requestInterval.length <= i) {
|
if (requestInterval.length <= i) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export 'msc_extensions/extension_timeline_export/timeline_export.dart';
|
||||||
export 'msc_extensions/msc_4140_delayed_events/api.dart';
|
export 'msc_extensions/msc_4140_delayed_events/api.dart';
|
||||||
|
|
||||||
export 'src/utils/web_worker/web_worker_stub.dart'
|
export 'src/utils/web_worker/web_worker_stub.dart'
|
||||||
if (dart.library.html) 'src/utils/web_worker/web_worker.dart';
|
if (dart.library.js_interop) 'src/utils/web_worker/web_worker.dart';
|
||||||
|
|
||||||
export 'src/utils/web_worker/native_implementations_web_worker_stub.dart'
|
export 'src/utils/web_worker/native_implementations_web_worker_stub.dart'
|
||||||
if (dart.library.html) 'src/utils/web_worker/native_implementations_web_worker.dart';
|
if (dart.library.js_interop) 'src/utils/web_worker/native_implementations_web_worker.dart';
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:matrix/matrix_api_lite/utils/print_logs_native.dart'
|
import 'package:matrix/matrix_api_lite/utils/print_logs_native.dart'
|
||||||
if (dart.library.html) 'print_logs_web.dart';
|
if (dart.library.js_interop) 'print_logs_web.dart';
|
||||||
|
|
||||||
enum Level {
|
enum Level {
|
||||||
wtf,
|
wtf,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
|
|
||||||
|
import 'package:web/web.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix_api_lite.dart';
|
import 'package:matrix/matrix_api_lite.dart';
|
||||||
|
|
||||||
|
|
@ -13,22 +15,22 @@ extension PrintLogs on LogEvent {
|
||||||
}
|
}
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case Level.wtf:
|
case Level.wtf:
|
||||||
window.console.error('!!!CRITICAL!!! $logsStr');
|
console.error('!!!CRITICAL!!! $logsStr'.toJS);
|
||||||
break;
|
break;
|
||||||
case Level.error:
|
case Level.error:
|
||||||
window.console.error(logsStr);
|
console.error(logsStr.toJS);
|
||||||
break;
|
break;
|
||||||
case Level.warning:
|
case Level.warning:
|
||||||
window.console.warn(logsStr);
|
console.warn(logsStr.toJS);
|
||||||
break;
|
break;
|
||||||
case Level.info:
|
case Level.info:
|
||||||
window.console.info(logsStr);
|
console.info(logsStr.toJS);
|
||||||
break;
|
break;
|
||||||
case Level.debug:
|
case Level.debug:
|
||||||
window.console.debug(logsStr);
|
console.debug(logsStr.toJS);
|
||||||
break;
|
break;
|
||||||
case Level.verbose:
|
case Level.verbose:
|
||||||
window.console.log(logsStr);
|
console.log(logsStr.toJS);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
import 'dart:indexed_db';
|
|
||||||
|
|
||||||
|
import 'package:web/web.dart';
|
||||||
|
|
||||||
|
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||||
import 'package:matrix/src/database/zone_transaction_mixin.dart';
|
import 'package:matrix/src/database/zone_transaction_mixin.dart';
|
||||||
|
|
||||||
/// Key-Value store abstraction over IndexedDB so that the sdk database can use
|
/// Key-Value store abstraction over IndexedDB so that the sdk database can use
|
||||||
/// a single interface for all platforms. API is inspired by Hive.
|
/// a single interface for all platforms. API is inspired by Hive.
|
||||||
class BoxCollection with ZoneTransactionMixin {
|
class BoxCollection with ZoneTransactionMixin {
|
||||||
final Database _db;
|
final IDBDatabase _db;
|
||||||
final Set<String> boxNames;
|
final Set<String> boxNames;
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
|
|
@ -18,23 +20,45 @@ class BoxCollection with ZoneTransactionMixin {
|
||||||
Set<String> boxNames, {
|
Set<String> boxNames, {
|
||||||
Object? sqfliteDatabase,
|
Object? sqfliteDatabase,
|
||||||
Object? sqfliteFactory,
|
Object? sqfliteFactory,
|
||||||
IdbFactory? idbFactory,
|
IDBFactory? idbFactory,
|
||||||
int version = 1,
|
int version = 1,
|
||||||
}) async {
|
}) async {
|
||||||
idbFactory ??= window.indexedDB!;
|
idbFactory ??= window.indexedDB;
|
||||||
final db = await idbFactory.open(
|
final dbOpenCompleter = Completer<BoxCollection>();
|
||||||
name,
|
final request = idbFactory.open(name, version);
|
||||||
version: version,
|
|
||||||
onUpgradeNeeded: (VersionChangeEvent event) {
|
request.onerror = (Event event) {
|
||||||
final db = event.target.result;
|
Logs().e(
|
||||||
|
'[IndexedDBBox] Error loading database - ${request.error?.toString()}',
|
||||||
|
);
|
||||||
|
dbOpenCompleter.completeError(
|
||||||
|
'Error loading database - ${request.error?.toString()}',
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
request.onupgradeneeded = (IDBVersionChangeEvent event) {
|
||||||
|
final db = (event.target! as IDBOpenDBRequest).result as IDBDatabase;
|
||||||
|
|
||||||
|
db.onerror = (Event event) {
|
||||||
|
Logs().e('[IndexedDBBox] [onupgradeneeded] Error loading database');
|
||||||
|
dbOpenCompleter
|
||||||
|
.completeError('Error loading database onupgradeneeded.');
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
for (final name in boxNames) {
|
for (final name in boxNames) {
|
||||||
if (db.objectStoreNames.contains(name)) continue;
|
if (db.objectStoreNames.contains(name)) continue;
|
||||||
|
db.createObjectStore(
|
||||||
db.createObjectStore(name, autoIncrement: true);
|
name,
|
||||||
}
|
IDBObjectStoreParameters(autoIncrement: true),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return BoxCollection(db, boxNames, name);
|
}
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
request.onsuccess = (Event event) {
|
||||||
|
final db = request.result as IDBDatabase;
|
||||||
|
dbOpenCompleter.complete(BoxCollection(db, boxNames, name));
|
||||||
|
}.toJS;
|
||||||
|
return dbOpenCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Box<V> openBox<V>(String name) {
|
Box<V> openBox<V>(String name) {
|
||||||
|
|
@ -44,7 +68,7 @@ class BoxCollection with ZoneTransactionMixin {
|
||||||
return Box<V>(name, this);
|
return Box<V>(name, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Future<void> Function(Transaction txn)>? _txnCache;
|
List<Future<void> Function(IDBTransaction txn)>? _txnCache;
|
||||||
|
|
||||||
Future<void> transaction(
|
Future<void> transaction(
|
||||||
Future<void> Function() action, {
|
Future<void> Function() action, {
|
||||||
|
|
@ -52,15 +76,18 @@ class BoxCollection with ZoneTransactionMixin {
|
||||||
bool readOnly = false,
|
bool readOnly = false,
|
||||||
}) =>
|
}) =>
|
||||||
zoneTransaction(() async {
|
zoneTransaction(() async {
|
||||||
boxNames ??= _db.objectStoreNames!.toList();
|
|
||||||
final txnCache = _txnCache = [];
|
final txnCache = _txnCache = [];
|
||||||
await action();
|
await action();
|
||||||
final cache =
|
final cache =
|
||||||
List<Future<void> Function(Transaction txn)>.from(txnCache);
|
List<Future<void> Function(IDBTransaction txn)>.from(txnCache);
|
||||||
_txnCache = null;
|
_txnCache = null;
|
||||||
if (cache.isEmpty) return;
|
if (cache.isEmpty) return;
|
||||||
final txn =
|
|
||||||
_db.transaction(boxNames, readOnly ? 'readonly' : 'readwrite');
|
final transactionCompleter = Completer<void>();
|
||||||
|
final txn = _db.transaction(
|
||||||
|
boxNames?.jsify() ?? _db.objectStoreNames,
|
||||||
|
readOnly ? 'readonly' : 'readwrite',
|
||||||
|
);
|
||||||
for (final fun in cache) {
|
for (final fun in cache) {
|
||||||
// The IDB methods return a Future in Dart but must not be awaited in
|
// The IDB methods return a Future in Dart but must not be awaited in
|
||||||
// order to have an actual transaction. They must only be performed and
|
// order to have an actual transaction. They must only be performed and
|
||||||
|
|
@ -69,16 +96,54 @@ class BoxCollection with ZoneTransactionMixin {
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction
|
// https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction
|
||||||
unawaited(fun(txn));
|
unawaited(fun(txn));
|
||||||
}
|
}
|
||||||
await txn.completed;
|
|
||||||
return;
|
txn.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [transaction] Error - ${txn.error?.toString()}',
|
||||||
|
);
|
||||||
|
transactionCompleter.completeError(
|
||||||
|
'Transaction not completed due to an error - ${txn.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
|
||||||
|
txn.oncomplete = (Event event) {
|
||||||
|
transactionCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
return transactionCompleter.future;
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
final txn = _db.transaction(boxNames.toList(), 'readwrite');
|
final transactionCompleter = Completer();
|
||||||
|
final txn = _db.transaction(boxNames.toList().jsify()!, 'readwrite');
|
||||||
for (final name in boxNames) {
|
for (final name in boxNames) {
|
||||||
unawaited(txn.objectStore(name).clear());
|
final objStoreClearCompleter = Completer();
|
||||||
|
final request = txn.objectStore(name).clear();
|
||||||
|
request.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [clear] Object store clear error - ${request.error?.toString()}',
|
||||||
|
);
|
||||||
|
objStoreClearCompleter.completeError(
|
||||||
|
'Object store clear not completed due to an error - ${request.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
request.onsuccess = (Event event) {
|
||||||
|
objStoreClearCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
unawaited(objStoreClearCompleter.future);
|
||||||
}
|
}
|
||||||
await txn.completed;
|
txn.onerror = (Event event) {
|
||||||
|
Logs().e('[IndexedDBBox] [clear] Error - ${txn.error?.toString()}');
|
||||||
|
transactionCompleter.completeError(
|
||||||
|
'DB clear transaction not completed due to an error - ${txn.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
txn.oncomplete = (Event event) {
|
||||||
|
transactionCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
return transactionCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
|
@ -87,13 +152,24 @@ class BoxCollection with ZoneTransactionMixin {
|
||||||
return zoneTransaction(() async => _db.close());
|
return zoneTransaction(() async => _db.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('use collection.deleteDatabase now')
|
|
||||||
static Future<void> delete(String name, [dynamic factory]) =>
|
|
||||||
(factory ?? window.indexedDB!).deleteDatabase(name);
|
|
||||||
|
|
||||||
Future<void> deleteDatabase(String name, [dynamic factory]) async {
|
Future<void> deleteDatabase(String name, [dynamic factory]) async {
|
||||||
await close();
|
await close();
|
||||||
await (factory ?? window.indexedDB).deleteDatabase(name);
|
final deleteDatabaseCompleter = Completer();
|
||||||
|
final request =
|
||||||
|
((factory ?? window.indexedDB) as IDBFactory).deleteDatabase(name);
|
||||||
|
request.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [deleteDatabase] Error - ${request.error?.toString()}',
|
||||||
|
);
|
||||||
|
deleteDatabaseCompleter.completeError(
|
||||||
|
'Error deleting database - ${request.error?.toString()}'.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
request.onsuccess = (Event event) {
|
||||||
|
Logs().i('[IndexedDBBox] [deleteDatabase] Database deleted.');
|
||||||
|
deleteDatabaseCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
return deleteDatabaseCompleter.future;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,44 +187,109 @@ class Box<V> {
|
||||||
|
|
||||||
Box(this.name, this.boxCollection);
|
Box(this.name, this.boxCollection);
|
||||||
|
|
||||||
Future<List<String>> getAllKeys([Transaction? txn]) async {
|
Future<List<String>> getAllKeys([IDBTransaction? txn]) async {
|
||||||
if (_quickAccessCachedKeys != null) return _quickAccessCachedKeys!.toList();
|
if (_quickAccessCachedKeys != null) return _quickAccessCachedKeys!.toList();
|
||||||
txn ??= boxCollection._db.transaction(name, 'readonly');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readonly');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
final request = store.getAllKeys(null);
|
final getAllKeysCompleter = Completer();
|
||||||
await request.onSuccess.first;
|
final request = store.getAllKeys();
|
||||||
final keys = request.result.cast<String>();
|
request.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [getAllKeys] Error - ${request.error?.toString()}',
|
||||||
|
);
|
||||||
|
getAllKeysCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [getAllKeys] Error - ${request.error?.toString()}'.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
request.onsuccess = (Event event) {
|
||||||
|
getAllKeysCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await getAllKeysCompleter.future;
|
||||||
|
final keys = (request.result?.dartify() as List?)?.cast<String>() ?? [];
|
||||||
_quickAccessCachedKeys = keys.toSet();
|
_quickAccessCachedKeys = keys.toSet();
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, V>> getAllValues([Transaction? txn]) async {
|
Future<Map<String, V>> getAllValues([IDBTransaction? txn]) async {
|
||||||
txn ??= boxCollection._db.transaction(name, 'readonly');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readonly');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
final map = <String, V>{};
|
final map = <String, V>{};
|
||||||
final cursorStream = store.openCursor(autoAdvance: true);
|
|
||||||
await for (final cursor in cursorStream) {
|
/// NOTE: This is a workaround to get the keys as [IDBObjectStore.getAll()]
|
||||||
map[cursor.key as String] = _fromValue(cursor.value) as V;
|
/// only returns the values as a list.
|
||||||
|
/// And using the [IDBObjectStore.openCursor()] method is not working as expected.
|
||||||
|
final keys = await getAllKeys(txn);
|
||||||
|
|
||||||
|
final getAllValuesCompleter = Completer();
|
||||||
|
final getAllValuesRequest = store.getAll();
|
||||||
|
getAllValuesRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [getAllValues] Error - ${getAllValuesRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
getAllValuesCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [getAllValues] Error - ${getAllValuesRequest.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
getAllValuesRequest.onsuccess = (Event event) {
|
||||||
|
final values = getAllValuesRequest.result.dartify() as List;
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
map[keys[i]] = _fromValue(values[i]) as V;
|
||||||
}
|
}
|
||||||
|
getAllValuesCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await getAllValuesCompleter.future;
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<V?> get(String key, [Transaction? txn]) async {
|
Future<V?> get(String key, [IDBTransaction? txn]) async {
|
||||||
if (_quickAccessCache.containsKey(key)) return _quickAccessCache[key];
|
if (_quickAccessCache.containsKey(key)) return _quickAccessCache[key];
|
||||||
txn ??= boxCollection._db.transaction(name, 'readonly');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readonly');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
_quickAccessCache[key] = await store.getObject(key).then(_fromValue);
|
final getObjectRequest = store.get(key.toJS);
|
||||||
|
final getObjectCompleter = Completer();
|
||||||
|
getObjectRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [get] Error - ${getObjectRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
getObjectCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [get] Error - ${getObjectRequest.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
getObjectRequest.onsuccess = (Event event) {
|
||||||
|
getObjectCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await getObjectCompleter.future;
|
||||||
|
_quickAccessCache[key] = _fromValue(getObjectRequest.result?.dartify());
|
||||||
return _quickAccessCache[key];
|
return _quickAccessCache[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async {
|
Future<List<V?>> getAll(List<String> keys, [IDBTransaction? txn]) async {
|
||||||
if (keys.every((key) => _quickAccessCache.containsKey(key))) {
|
if (keys.every((key) => _quickAccessCache.containsKey(key))) {
|
||||||
return keys.map((key) => _quickAccessCache[key]).toList();
|
return keys.map((key) => _quickAccessCache[key]).toList();
|
||||||
}
|
}
|
||||||
txn ??= boxCollection._db.transaction(name, 'readonly');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readonly');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
final list = await Future.wait(
|
final list = await Future.wait(
|
||||||
keys.map((key) => store.getObject(key).then(_fromValue)),
|
keys.map((key) async {
|
||||||
|
final getObjectRequest = store.get(key.toJS);
|
||||||
|
final getObjectCompleter = Completer();
|
||||||
|
getObjectRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [getAll] Error at key $key - ${getObjectRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
getObjectCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [getAll] Error at key $key - ${getObjectRequest.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
getObjectRequest.onsuccess = (Event event) {
|
||||||
|
getObjectCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await getObjectCompleter.future;
|
||||||
|
return _fromValue(getObjectRequest.result?.dartify());
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
_quickAccessCache[keys[i]] = list[i];
|
_quickAccessCache[keys[i]] = list[i];
|
||||||
|
|
@ -156,7 +297,7 @@ class Box<V> {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> put(String key, V val, [Transaction? txn]) async {
|
Future<void> put(String key, V val, [IDBTransaction? txn]) async {
|
||||||
if (boxCollection._txnCache != null) {
|
if (boxCollection._txnCache != null) {
|
||||||
boxCollection._txnCache!.add((txn) => put(key, val, txn));
|
boxCollection._txnCache!.add((txn) => put(key, val, txn));
|
||||||
_quickAccessCache[key] = val;
|
_quickAccessCache[key] = val;
|
||||||
|
|
@ -164,15 +305,28 @@ class Box<V> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
txn ??= boxCollection._db.transaction(name, 'readwrite');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readwrite');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
await store.put(val as Object, key);
|
final putRequest = store.put(val.jsify(), key.toJS);
|
||||||
|
final putCompleter = Completer();
|
||||||
|
putRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [put] Error - ${putRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
putCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [put] Error - ${putRequest.error?.toString()}'.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
putRequest.onsuccess = (Event event) {
|
||||||
|
putCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await putCompleter.future;
|
||||||
_quickAccessCache[key] = val;
|
_quickAccessCache[key] = val;
|
||||||
_quickAccessCachedKeys?.add(key);
|
_quickAccessCachedKeys?.add(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> delete(String key, [Transaction? txn]) async {
|
Future<void> delete(String key, [IDBTransaction? txn]) async {
|
||||||
if (boxCollection._txnCache != null) {
|
if (boxCollection._txnCache != null) {
|
||||||
boxCollection._txnCache!.add((txn) => delete(key, txn));
|
boxCollection._txnCache!.add((txn) => delete(key, txn));
|
||||||
_quickAccessCache[key] = null;
|
_quickAccessCache[key] = null;
|
||||||
|
|
@ -180,9 +334,23 @@ class Box<V> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
txn ??= boxCollection._db.transaction(name, 'readwrite');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readwrite');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
await store.delete(key);
|
final deleteRequest = store.delete(key.toJS);
|
||||||
|
final deleteCompleter = Completer();
|
||||||
|
deleteRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [delete] Error - ${deleteRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
deleteCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [delete] Error - ${deleteRequest.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
deleteRequest.onsuccess = (Event event) {
|
||||||
|
deleteCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await deleteCompleter.future;
|
||||||
|
|
||||||
// Set to null instead remove() so that inside of transactions null is
|
// Set to null instead remove() so that inside of transactions null is
|
||||||
// returned.
|
// returned.
|
||||||
|
|
@ -191,7 +359,7 @@ class Box<V> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteAll(List<String> keys, [Transaction? txn]) async {
|
Future<void> deleteAll(List<String> keys, [IDBTransaction? txn]) async {
|
||||||
if (boxCollection._txnCache != null) {
|
if (boxCollection._txnCache != null) {
|
||||||
boxCollection._txnCache!.add((txn) => deleteAll(keys, txn));
|
boxCollection._txnCache!.add((txn) => deleteAll(keys, txn));
|
||||||
for (final key in keys) {
|
for (final key in keys) {
|
||||||
|
|
@ -201,10 +369,24 @@ class Box<V> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
txn ??= boxCollection._db.transaction(name, 'readwrite');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readwrite');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
for (final key in keys) {
|
for (final key in keys) {
|
||||||
await store.delete(key);
|
final deleteRequest = store.delete(key.toJS);
|
||||||
|
final deleteCompleter = Completer();
|
||||||
|
deleteRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [deleteAll] Error at key $key - ${deleteRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
deleteCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [deleteAll] Error at key $key - ${deleteRequest.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
deleteRequest.onsuccess = (Event event) {
|
||||||
|
deleteCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await deleteCompleter.future;
|
||||||
_quickAccessCache[key] = null;
|
_quickAccessCache[key] = null;
|
||||||
_quickAccessCachedKeys?.remove(key);
|
_quickAccessCachedKeys?.remove(key);
|
||||||
}
|
}
|
||||||
|
|
@ -216,15 +398,28 @@ class Box<V> {
|
||||||
_quickAccessCachedKeys = null;
|
_quickAccessCachedKeys = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clear([Transaction? txn]) async {
|
Future<void> clear([IDBTransaction? txn]) async {
|
||||||
if (boxCollection._txnCache != null) {
|
if (boxCollection._txnCache != null) {
|
||||||
boxCollection._txnCache!.add((txn) => clear(txn));
|
boxCollection._txnCache!.add((txn) => clear(txn));
|
||||||
} else {
|
} else {
|
||||||
txn ??= boxCollection._db.transaction(name, 'readwrite');
|
txn ??= boxCollection._db.transaction(name.toJS, 'readwrite');
|
||||||
final store = txn.objectStore(name);
|
final store = txn.objectStore(name);
|
||||||
await store.clear();
|
final clearRequest = store.clear();
|
||||||
|
final clearCompleter = Completer();
|
||||||
|
clearRequest.onerror = (Event event) {
|
||||||
|
Logs().e(
|
||||||
|
'[IndexedDBBox] [clear] Error - ${clearRequest.error?.toString()}',
|
||||||
|
);
|
||||||
|
clearCompleter.completeError(
|
||||||
|
'[IndexedDBBox] [clear] Error - ${clearRequest.error?.toString()}'
|
||||||
|
.toJS,
|
||||||
|
);
|
||||||
|
}.toJS;
|
||||||
|
clearRequest.onsuccess = (Event event) {
|
||||||
|
clearCompleter.complete();
|
||||||
|
}.toJS;
|
||||||
|
await clearCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearQuickAccessCache();
|
clearQuickAccessCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ import 'package:matrix/src/utils/copy_map.dart';
|
||||||
import 'package:matrix/src/utils/queued_to_device_event.dart';
|
import 'package:matrix/src/utils/queued_to_device_event.dart';
|
||||||
import 'package:matrix/src/utils/run_benchmarked.dart';
|
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||||
|
|
||||||
import 'package:matrix/src/database/indexeddb_box.dart'
|
import 'package:matrix/src/database/sqflite_box.dart'
|
||||||
if (dart.library.io) 'package:matrix/src/database/sqflite_box.dart';
|
if (dart.library.js_interop) 'package:matrix/src/database/indexeddb_box.dart';
|
||||||
|
|
||||||
import 'package:matrix/src/database/database_file_storage_stub.dart'
|
import 'package:matrix/src/database/database_file_storage_stub.dart'
|
||||||
if (dart.library.io) 'package:matrix/src/database/database_file_storage_io.dart';
|
if (dart.library.io) 'package:matrix/src/database/database_file_storage_io.dart';
|
||||||
|
|
@ -167,8 +167,8 @@ class MatrixSdkDatabase extends DatabaseApi with DatabaseFileStorage {
|
||||||
|
|
||||||
Database? database;
|
Database? database;
|
||||||
|
|
||||||
/// Custom IdbFactory used to create the indexedDB. On IO platforms it would
|
/// Custom [IDBFactory] used to create the indexedDB. On IO platforms it would
|
||||||
/// lead to an error to import "dart:indexed_db" so this is dynamically
|
/// lead to an error to import "package:web/web.dart" so this is dynamically
|
||||||
/// typed.
|
/// typed.
|
||||||
final dynamic idbFactory;
|
final dynamic idbFactory;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export 'native.dart' if (dart.library.js) 'js.dart';
|
export 'native.dart' if (dart.library.js_interop) 'js.dart';
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:html';
|
import 'dart:js_interop';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:web/web.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
class NativeImplementationsWebWorker extends NativeImplementations {
|
class NativeImplementationsWebWorker extends NativeImplementations {
|
||||||
|
|
@ -23,8 +25,8 @@ class NativeImplementationsWebWorker extends NativeImplementations {
|
||||||
Uri href, {
|
Uri href, {
|
||||||
this.timeout = const Duration(seconds: 30),
|
this.timeout = const Duration(seconds: 30),
|
||||||
this.onStackTrace = defaultStackTraceHandler,
|
this.onStackTrace = defaultStackTraceHandler,
|
||||||
}) : worker = Worker(href.toString()) {
|
}) : worker = Worker(href.toString().toJS) {
|
||||||
worker.onMessage.listen(_handleIncomingMessage);
|
worker.onmessage = _handleIncomingMessage.toJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T> operation<T, U>(WebWorkerOperations name, U argument) async {
|
Future<T> operation<T, U>(WebWorkerOperations name, U argument) async {
|
||||||
|
|
@ -32,27 +34,26 @@ class NativeImplementationsWebWorker extends NativeImplementations {
|
||||||
final completer = Completer<T>();
|
final completer = Completer<T>();
|
||||||
_completers[label] = completer;
|
_completers[label] = completer;
|
||||||
final message = WebWorkerData(label, name, argument);
|
final message = WebWorkerData(label, name, argument);
|
||||||
worker.postMessage(message.toJson());
|
worker.postMessage(message.toJson().jsify());
|
||||||
|
|
||||||
return completer.future.timeout(timeout);
|
return completer.future.timeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleIncomingMessage(MessageEvent event) async {
|
void _handleIncomingMessage(MessageEvent event) async {
|
||||||
final data = event.data;
|
final data = event.data.dartify() as LinkedHashMap;
|
||||||
// don't forget handling errors of our second thread...
|
// don't forget handling errors of our second thread...
|
||||||
if (data['label'] == 'stacktrace') {
|
if (data['label'] == 'stacktrace') {
|
||||||
final origin = event.data['origin'];
|
final origin = data['origin'];
|
||||||
final completer = _completers[origin];
|
final completer = _completers[origin];
|
||||||
|
|
||||||
final error = event.data['error']!;
|
final error = data['error']!;
|
||||||
|
|
||||||
final stackTrace =
|
final stackTrace = await onStackTrace.call(data['stacktrace'] as String);
|
||||||
await onStackTrace.call(event.data['stacktrace'] as String);
|
|
||||||
completer?.completeError(
|
completer?.completeError(
|
||||||
WebWorkerError(error: error, stackTrace: stackTrace),
|
WebWorkerError(error: error, stackTrace: stackTrace),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final response = WebWorkerData.fromJson(event.data);
|
final response = WebWorkerData.fromJson(data);
|
||||||
_completers[response.label]!.complete(response.data);
|
_completers[response.label]!.complete(response.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:collection';
|
||||||
import 'dart:indexed_db';
|
import 'dart:js_interop';
|
||||||
import 'dart:js';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:js/js.dart';
|
import 'package:web/web.dart';
|
||||||
import 'package:js/js_util.dart';
|
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart' hide Event;
|
import 'package:matrix/matrix.dart' hide Event;
|
||||||
import 'package:matrix/src/utils/web_worker/native_implementations_web_worker.dart';
|
import 'package:matrix/src/utils/web_worker/native_implementations_web_worker.dart';
|
||||||
|
|
@ -32,15 +30,15 @@ import 'package:matrix/src/utils/web_worker/native_implementations_web_worker.da
|
||||||
/// the web worker in your CI pipeline.
|
/// the web worker in your CI pipeline.
|
||||||
///
|
///
|
||||||
|
|
||||||
|
DedicatedWorkerGlobalScope get _workerScope =>
|
||||||
|
(globalContext as DedicatedWorkerGlobalScope).self
|
||||||
|
as DedicatedWorkerGlobalScope;
|
||||||
|
|
||||||
@pragma('dart2js:tryInline')
|
@pragma('dart2js:tryInline')
|
||||||
Future<void> startWebWorker() async {
|
Future<void> startWebWorker() async {
|
||||||
print('[native implementations worker]: Starting...');
|
Logs().i('[native implementations worker]: Starting...');
|
||||||
setProperty(
|
_workerScope.onmessage = (MessageEvent event) {
|
||||||
context['self'] as Object,
|
final data = event.data.dartify() as LinkedHashMap;
|
||||||
'onmessage',
|
|
||||||
allowInterop(
|
|
||||||
(MessageEvent event) async {
|
|
||||||
final data = event.data;
|
|
||||||
try {
|
try {
|
||||||
final operation = WebWorkerData.fromJson(data);
|
final operation = WebWorkerData.fromJson(data);
|
||||||
switch (operation.name) {
|
switch (operation.name) {
|
||||||
|
|
@ -50,45 +48,55 @@ Future<void> startWebWorker() async {
|
||||||
Map.from(operation.data as Map),
|
Map.from(operation.data as Map),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
sendResponse(operation.label as double, result?.toJson());
|
_sendResponse(
|
||||||
|
operation.label as double,
|
||||||
|
result?.toJson(),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case WebWorkerOperations.calcImageMetadata:
|
case WebWorkerOperations.calcImageMetadata:
|
||||||
final result = MatrixImageFile.calcMetadataImplementation(
|
final result = MatrixImageFile.calcMetadataImplementation(
|
||||||
Uint8List.fromList(
|
Uint8List.fromList(
|
||||||
(operation.data as JsArray).whereType<int>().toList(),
|
(operation.data as List).whereType<int>().toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
sendResponse(operation.label as double, result?.toJson());
|
_sendResponse(
|
||||||
|
operation.label as double,
|
||||||
|
result?.toJson(),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw TypeError();
|
throw TypeError();
|
||||||
}
|
}
|
||||||
} on Event catch (e, s) {
|
|
||||||
allowInterop(_replyError)
|
|
||||||
.call((e.target as Request).error, s, data['label'] as double);
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
allowInterop(_replyError).call(e, s, data['label'] as double);
|
_replyError(e, s, data['label'] as double);
|
||||||
}
|
}
|
||||||
},
|
}.toJS;
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendResponse(double label, dynamic response) {
|
void _sendResponse(
|
||||||
|
double label,
|
||||||
|
dynamic response,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
self.postMessage({
|
_workerScope.postMessage(
|
||||||
|
{
|
||||||
'label': label,
|
'label': label,
|
||||||
'data': response,
|
'data': response,
|
||||||
});
|
}.jsify(),
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
print('[native implementations worker] Error responding: $e, $s');
|
Logs().e('[native implementations worker] Error responding: $e, $s');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _replyError(Object? error, StackTrace stackTrace, double origin) {
|
void _replyError(
|
||||||
|
Object? error,
|
||||||
|
StackTrace stackTrace,
|
||||||
|
double origin,
|
||||||
|
) {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
try {
|
try {
|
||||||
final jsError = jsify(error);
|
final jsError = error.jsify();
|
||||||
if (jsError != null) {
|
if (jsError != null) {
|
||||||
error = jsError;
|
error = jsError;
|
||||||
}
|
}
|
||||||
|
|
@ -97,24 +105,15 @@ void _replyError(Object? error, StackTrace stackTrace, double origin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
self.postMessage({
|
_workerScope.postMessage(
|
||||||
|
{
|
||||||
'label': 'stacktrace',
|
'label': 'stacktrace',
|
||||||
'origin': origin,
|
'origin': origin,
|
||||||
'error': error,
|
'error': error,
|
||||||
'stacktrace': stackTrace.toString(),
|
'stacktrace': stackTrace.toString(),
|
||||||
});
|
}.jsify(),
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
print('[native implementations worker] Error responding: $e, $s');
|
Logs().e('[native implementations worker] Error responding: $e, $s');
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// represents the [WorkerGlobalScope] the worker currently runs in.
|
|
||||||
@JS('self')
|
|
||||||
external WorkerGlobalScope get self;
|
|
||||||
|
|
||||||
/// adding all missing WebWorker-only properties to the [WorkerGlobalScope]
|
|
||||||
extension on WorkerGlobalScope {
|
|
||||||
void postMessage(Object data) {
|
|
||||||
callMethod(self, 'postMessage', [jsify(data)]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class WrappedMediaStream {
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
// AOT it
|
// AOT it
|
||||||
const isWeb = bool.fromEnvironment('dart.library.js_util');
|
const isWeb = bool.fromEnvironment('dart.library.js_interop');
|
||||||
|
|
||||||
// libwebrtc does not provide a way to clone MediaStreams. So stopping the
|
// libwebrtc does not provide a way to clone MediaStreams. So stopping the
|
||||||
// local stream here would break calls with all other participants if anyone
|
// local stream here would break calls with all other participants if anyone
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ dependencies:
|
||||||
http: ">=0.13.0 <2.0.0"
|
http: ">=0.13.0 <2.0.0"
|
||||||
image: ^4.0.15
|
image: ^4.0.15
|
||||||
js: ^0.6.3
|
js: ^0.6.3
|
||||||
|
js_interop: ^0.0.1
|
||||||
markdown: ^7.1.1
|
markdown: ^7.1.1
|
||||||
mime: ">=1.0.0 <3.0.0"
|
mime: ">=1.0.0 <3.0.0"
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
|
|
@ -32,6 +33,7 @@ dependencies:
|
||||||
sqlite3: ^2.1.0
|
sqlite3: ^2.1.0
|
||||||
typed_data: ^1.3.2
|
typed_data: ^1.3.2
|
||||||
vodozemac: ^0.2.0
|
vodozemac: ^0.2.0
|
||||||
|
web: ^1.1.1
|
||||||
webrtc_interface: ^1.2.0
|
webrtc_interface: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'package:matrix/src/database/indexeddb_box.dart'
|
import 'package:matrix/src/database/sqflite_box.dart'
|
||||||
if (dart.library.io) 'package:matrix/src/database/sqflite_box.dart';
|
if (dart.library.js_interop) 'package:matrix/src/database/indexeddb_box.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('Box tests', () {
|
group('Box tests', () {
|
||||||
|
|
@ -11,7 +11,7 @@ void main() {
|
||||||
const data = {'name': 'Fluffy', 'age': 2};
|
const data = {'name': 'Fluffy', 'age': 2};
|
||||||
const data2 = {'name': 'Loki', 'age': 4};
|
const data2 = {'name': 'Loki', 'age': 4};
|
||||||
Database? db;
|
Database? db;
|
||||||
const isWeb = bool.fromEnvironment('dart.library.js_util');
|
const isWeb = bool.fromEnvironment('dart.library.js_interop');
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
if (!isWeb) {
|
if (!isWeb) {
|
||||||
db = await databaseFactoryFfi.openDatabase(':memory:');
|
db = await databaseFactoryFfi.openDatabase(':memory:');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue