import 'veilid.dart'; import 'dart:html' as html; import 'dart:js' as js; import 'dart:js_util' as js_util; import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'veilid_encoding.dart'; ////////////////////////////////////////////////////////// // WASM Platform-specific config class VeilidWASMConfigLoggingPerformance { bool enabled; VeilidConfigLogLevel level; bool logsInTimings; bool logsInConsole; VeilidWASMConfigLoggingPerformance({ required this.enabled, required this.level, required this.logsInTimings, required this.logsInConsole, }); Map get json { return { 'enabled': enabled, 'level': level.json, 'logs_in_timings': logsInTimings, 'logs_in_console': logsInConsole, }; } VeilidWASMConfigLoggingPerformance.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']), logsInTimings = json['logs_in_timings'], logsInConsole = json['logs_in_console']; } class VeilidWASMConfigLoggingApi { bool enabled; VeilidConfigLogLevel level; VeilidWASMConfigLoggingApi({ required this.enabled, required this.level, }); Map get json { return { 'enabled': enabled, 'level': level.json, }; } VeilidWASMConfigLoggingApi.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']); } class VeilidWASMConfigLogging { VeilidWASMConfigLoggingPerformance performance; VeilidWASMConfigLoggingApi api; VeilidWASMConfigLogging({required this.performance, required this.api}); Map get json { return { 'performance': performance.json, 'api': api.json, }; } VeilidWASMConfigLogging.fromJson(dynamic json) : performance = VeilidWASMConfigLoggingPerformance.fromJson(json['performance']), api = VeilidWASMConfigLoggingApi.fromJson(json['api']); } class VeilidWASMConfig { VeilidWASMConfigLogging logging; VeilidWASMConfig({ required this.logging, }); Map get json { return { 'logging': logging.json, }; } VeilidWASMConfig.fromJson(dynamic json) : logging = VeilidWASMConfigLogging.fromJson(json['logging']); } ////////////////////////////////////////////////////////// Veilid getVeilid() => VeilidJS(); Object wasm = js_util.getProperty(html.window, "veilid_wasm"); Future _wrapApiPromise(Object p) { return js_util.promiseToFuture(p).then((value) => value as T).catchError( (error) => Future.error( VeilidAPIException.fromJson(jsonDecode(error as String)))); } class _Ctx { final int id; final VeilidJS js; _Ctx(this.id, this.js); } // JS implementation of VeilidRoutingContext class VeilidRoutingContextJS implements VeilidRoutingContext { final _Ctx _ctx; static final Finalizer<_Ctx> _finalizer = Finalizer((ctx) => { js_util.callMethod(wasm, "release_routing_context", [ctx.id]) }); VeilidRoutingContextJS._(this._ctx) { _finalizer.attach(this, _ctx, detach: this); } @override VeilidRoutingContextJS withPrivacy() { int newId = js_util.callMethod(wasm, "routing_context_with_privacy", [_ctx.id]); return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override VeilidRoutingContextJS withCustomPrivacy(Stability stability) { final newId = js_util.callMethod( wasm, "routing_context_with_custom_privacy", [_ctx.id, stability.json]); return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override VeilidRoutingContextJS withSequencing(Sequencing sequencing) { final newId = js_util.callMethod( wasm, "routing_context_with_sequencing", [_ctx.id, sequencing.json]); return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override Future appCall(String target, Uint8List request) async { var encodedRequest = base64UrlNoPadEncode(request); return base64UrlNoPadDecode(await _wrapApiPromise(js_util.callMethod( wasm, "routing_context_app_call", [_ctx.id, target, encodedRequest]))); } @override Future appMessage(String target, Uint8List message) { var encodedMessage = base64UrlNoPadEncode(message); return _wrapApiPromise(js_util.callMethod(wasm, "routing_context_app_message", [_ctx.id, target, encodedMessage])); } } class _TDBT { final int id; VeilidTableDBJS tdbjs; VeilidJS js; _TDBT(this.id, this.tdbjs, this.js); } // JS implementation of VeilidTableDBTransaction class VeilidTableDBTransactionJS extends VeilidTableDBTransaction { final _TDBT _tdbt; static final Finalizer<_TDBT> _finalizer = Finalizer((tdbt) => { js_util.callMethod(wasm, "release_table_db_transaction", [tdbt.id]) }); VeilidTableDBTransactionJS._(this._tdbt) { _finalizer.attach(this, _tdbt, detach: this); } @override Future commit() { return _wrapApiPromise( js_util.callMethod(wasm, "table_db_transaction_commit", [_tdbt.id])); } @override Future rollback() { return _wrapApiPromise( js_util.callMethod(wasm, "table_db_transaction_rollback", [_tdbt.id])); } @override Future store(int col, Uint8List key, Uint8List value) { final encodedKey = base64UrlNoPadEncode(key); final encodedValue = base64UrlNoPadEncode(value); return _wrapApiPromise(js_util.callMethod( wasm, "table_db_transaction_store", [_tdbt.id, col, encodedKey, encodedValue])); } @override Future delete(int col, Uint8List key) { final encodedKey = base64UrlNoPadEncode(key); return _wrapApiPromise(js_util.callMethod( wasm, "table_db_transaction_delete", [_tdbt.id, col, encodedKey])); } } class _TDB { final int id; VeilidJS js; _TDB(this.id, this.js); } // JS implementation of VeilidTableDB class VeilidTableDBJS extends VeilidTableDB { final _TDB _tdb; static final Finalizer<_TDB> _finalizer = Finalizer((tdb) => { js_util.callMethod(wasm, "release_table_db", [tdb.id]) }); VeilidTableDBJS._(this._tdb) { _finalizer.attach(this, _tdb, detach: this); } @override int getColumnCount() { return js_util.callMethod(wasm, "table_db_get_column_count", [_tdb.id]); } @override List getKeys(int col) { String? s = js_util.callMethod(wasm, "table_db_get_keys", [_tdb.id, col]); if (s == null) { throw VeilidAPIExceptionInternal("No db for id"); } List jarr = jsonDecode(s); return jarr.map((e) => base64UrlNoPadDecode(e)).toList(); } @override VeilidTableDBTransaction transact() { final id = js_util.callMethod(wasm, "table_db_transact", [_tdb.id]); return VeilidTableDBTransactionJS._(_TDBT(id, this, _tdb.js)); } @override Future store(int col, Uint8List key, Uint8List value) { final encodedKey = base64UrlNoPadEncode(key); final encodedValue = base64UrlNoPadEncode(value); return _wrapApiPromise(js_util.callMethod( wasm, "table_db_store", [_tdb.id, col, encodedKey, encodedValue])); } @override Future load(int col, Uint8List key) async { final encodedKey = base64UrlNoPadEncode(key); String? out = await _wrapApiPromise( js_util.callMethod(wasm, "table_db_load", [_tdb.id, col, encodedKey])); if (out == null) { return null; } return base64UrlNoPadDecode(out); } @override Future delete(int col, Uint8List key) { final encodedKey = base64UrlNoPadEncode(key); return _wrapApiPromise(js_util .callMethod(wasm, "table_db_delete", [_tdb.id, col, encodedKey])); } } // JS implementation of high level Veilid API class VeilidJS implements Veilid { @override void initializeVeilidCore(Map platformConfigJson) { var platformConfigJsonString = jsonEncode(platformConfigJson, toEncodable: veilidApiToEncodable); js_util .callMethod(wasm, "initialize_veilid_core", [platformConfigJsonString]); } @override void changeLogLevel(String layer, VeilidConfigLogLevel logLevel) { var logLevelJsonString = jsonEncode(logLevel.json, toEncodable: veilidApiToEncodable); js_util.callMethod(wasm, "change_log_level", [layer, logLevelJsonString]); } @override Future> startupVeilidCore(VeilidConfig config) async { var streamController = StreamController(); updateCallback(String update) { var updateJson = jsonDecode(update); if (updateJson["kind"] == "Shutdown") { streamController.close(); } else { var update = VeilidUpdate.fromJson(updateJson); streamController.add(update); } } await _wrapApiPromise(js_util.callMethod(wasm, "startup_veilid_core", [ js.allowInterop(updateCallback), jsonEncode(config.json, toEncodable: veilidApiToEncodable) ])); return streamController.stream; } @override Future getVeilidState() async { return VeilidState.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, "get_veilid_state", [])))); } @override Future attach() { return _wrapApiPromise(js_util.callMethod(wasm, "attach", [])); } @override Future detach() { return _wrapApiPromise(js_util.callMethod(wasm, "detach", [])); } @override Future shutdownVeilidCore() { return _wrapApiPromise( js_util.callMethod(wasm, "shutdown_veilid_core", [])); } @override Future routingContext() async { int id = await _wrapApiPromise(js_util.callMethod(wasm, "routing_context", [])); return VeilidRoutingContextJS._(_Ctx(id, this)); } @override Future newPrivateRoute() async { Map blobJson = jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, "new_private_route", []))); return RouteBlob.fromJson(blobJson); } @override Future newCustomPrivateRoute( Stability stability, Sequencing sequencing) async { var stabilityString = jsonEncode(stability, toEncodable: veilidApiToEncodable); var sequencingString = jsonEncode(sequencing, toEncodable: veilidApiToEncodable); Map blobJson = jsonDecode(await _wrapApiPromise(js_util .callMethod( wasm, "new_private_route", [stabilityString, sequencingString]))); return RouteBlob.fromJson(blobJson); } @override Future importRemotePrivateRoute(Uint8List blob) { var encodedBlob = base64UrlNoPadEncode(blob); return _wrapApiPromise( js_util.callMethod(wasm, "import_remote_private_route", [encodedBlob])); } @override Future releasePrivateRoute(String key) { return _wrapApiPromise( js_util.callMethod(wasm, "release_private_route", [key])); } @override Future appCallReply(String id, Uint8List message) { var encodedMessage = base64UrlNoPadEncode(message); return _wrapApiPromise( js_util.callMethod(wasm, "app_call_reply", [id, encodedMessage])); } @override Future openTableDB(String name, int columnCount) async { int id = await _wrapApiPromise( js_util.callMethod(wasm, "open_table_db", [name, columnCount])); return VeilidTableDBJS._(_TDB(id, this)); } @override Future deleteTableDB(String name) { return _wrapApiPromise(js_util.callMethod(wasm, "delete_table_db", [name])); } @override Future debug(String command) async { return await _wrapApiPromise(js_util.callMethod(wasm, "debug", [command])); } @override String veilidVersionString() { return js_util.callMethod(wasm, "veilid_version_string", []); } @override VeilidVersion veilidVersion() { Map jsonVersion = jsonDecode(js_util.callMethod(wasm, "veilid_version", [])); return VeilidVersion( jsonVersion["major"], jsonVersion["minor"], jsonVersion["patch"]); } }