import 'dart:async'; import 'dart:convert'; import 'dart:html' as html; import 'dart:js' as js; import 'dart:js_util' as js_util; import 'dart:typed_data'; import 'veilid.dart'; ////////////////////////////////////////////////////////// Veilid getVeilid() => VeilidJS(); Object wasm = js_util.getProperty(html.window, 'veilid_wasm'); Future _wrapApiPromise(Object p) => js_util.promiseToFuture(p).then((value) => value as T).catchError( (error) => Future.error( VeilidAPIException.fromJson(jsonDecode(error as String)))); class _Ctx { _Ctx(int this.id, this.js); int? id; final VeilidJS js; void ensureValid() { if (id == null) { throw VeilidAPIExceptionNotInitialized(); } } void close() { if (id != null) { js_util.callMethod(wasm, 'release_routing_context', [id!]); id = null; } } } // JS implementation of VeilidRoutingContext class VeilidRoutingContextJS extends VeilidRoutingContext { VeilidRoutingContextJS._(this._ctx) { _finalizer.attach(this, _ctx, detach: this); } final _Ctx _ctx; static final Finalizer<_Ctx> _finalizer = Finalizer((ctx) => ctx.close()); @override void close() { _ctx.close(); } @override VeilidRoutingContextJS withPrivacy() { _ctx.ensureValid(); final int newId = js_util.callMethod(wasm, 'routing_context_with_privacy', [_ctx.id!]); return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override VeilidRoutingContextJS withCustomPrivacy(SafetySelection safetySelection) { _ctx.ensureValid(); final newId = js_util.callMethod( wasm, 'routing_context_with_custom_privacy', [_ctx.id!, jsonEncode(safetySelection)]); return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override VeilidRoutingContextJS withSequencing(Sequencing sequencing) { _ctx.ensureValid(); final newId = js_util.callMethod(wasm, 'routing_context_with_sequencing', [_ctx.id!, jsonEncode(sequencing)]); return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); } @override Future appCall(String target, Uint8List request) async { _ctx.ensureValid(); final 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) { _ctx.ensureValid(); final encodedMessage = base64UrlNoPadEncode(message); return _wrapApiPromise(js_util.callMethod(wasm, 'routing_context_app_message', [_ctx.id!, target, encodedMessage])); } @override Future createDHTRecord(DHTSchema schema, {CryptoKind kind = 0}) async { _ctx.ensureValid(); return DHTRecordDescriptor.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'routing_context_create_dht_record', [_ctx.id!, jsonEncode(schema), kind])))); } @override Future openDHTRecord( TypedKey key, KeyPair? writer) async { _ctx.ensureValid(); return DHTRecordDescriptor.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'routing_context_open_dht_record', [ _ctx.id!, jsonEncode(key), if (writer != null) jsonEncode(writer) else null ])))); } @override Future closeDHTRecord(TypedKey key) { _ctx.ensureValid(); return _wrapApiPromise(js_util.callMethod( wasm, 'routing_context_close_dht_record', [_ctx.id!, jsonEncode(key)])); } @override Future deleteDHTRecord(TypedKey key) { _ctx.ensureValid(); return _wrapApiPromise(js_util.callMethod(wasm, 'routing_context_delete_dht_record', [_ctx.id!, jsonEncode(key)])); } @override Future getDHTValue( TypedKey key, int subkey, bool forceRefresh) async { _ctx.ensureValid(); final opt = await _wrapApiPromise(js_util.callMethod( wasm, 'routing_context_get_dht_value', [_ctx.id!, jsonEncode(key), subkey, forceRefresh])); return opt == null ? null : ValueData.fromJson(jsonDecode(opt)); } @override Future setDHTValue( TypedKey key, int subkey, Uint8List data) async { _ctx.ensureValid(); final opt = await _wrapApiPromise(js_util.callMethod( wasm, 'routing_context_set_dht_value', [_ctx.id!, jsonEncode(key), subkey, base64UrlNoPadEncode(data)])); return opt == null ? null : ValueData.fromJson(jsonDecode(opt)); } @override Future watchDHTValues(TypedKey key, List subkeys, Timestamp expiration, int count) async { _ctx.ensureValid(); final ts = await _wrapApiPromise(js_util.callMethod( wasm, 'routing_context_watch_dht_values', [ _ctx.id!, jsonEncode(key), jsonEncode(subkeys), expiration.toString(), count ])); return Timestamp.fromString(ts); } @override Future cancelDHTWatch(TypedKey key, List subkeys) { _ctx.ensureValid(); return _wrapApiPromise(js_util.callMethod( wasm, 'routing_context_cancel_dht_watch', [_ctx.id!, jsonEncode(key), jsonEncode(subkeys)])); } } // JS implementation of VeilidCryptoSystem class VeilidCryptoSystemJS extends VeilidCryptoSystem { VeilidCryptoSystemJS._(this._js, this._kind) { // Keep the reference _js; } final CryptoKind _kind; final VeilidJS _js; @override CryptoKind kind() => _kind; @override Future cachedDH(PublicKey key, SecretKey secret) async => SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'crypto_cached_dh', [_kind, jsonEncode(key), jsonEncode(secret)])))); @override Future computeDH(PublicKey key, SecretKey secret) async => SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'crypto_compute_dh', [_kind, jsonEncode(key), jsonEncode(secret)])))); @override Future randomBytes(int len) async => base64UrlNoPadDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'crypto_random_bytes', [_kind, len]))); @override Future defaultSaltLength() => _wrapApiPromise( js_util.callMethod(wasm, 'crypto_default_salt_length', [_kind])); @override Future hashPassword(Uint8List password, Uint8List salt) => _wrapApiPromise(js_util.callMethod(wasm, 'crypto_hash_password', [_kind, base64UrlNoPadEncode(password), base64UrlNoPadEncode(salt)])); @override Future verifyPassword(Uint8List password, String passwordHash) => _wrapApiPromise(js_util.callMethod(wasm, 'crypto_verify_password', [_kind, base64UrlNoPadEncode(password), passwordHash])); @override Future deriveSharedSecret( Uint8List password, Uint8List salt) async => SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'crypto_derive_shared_secret', [ _kind, base64UrlNoPadEncode(password), base64UrlNoPadEncode(salt) ])))); @override Future randomNonce() async => Nonce.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'crypto_random_nonce', [_kind])))); @override Future randomSharedSecret() async => SharedSecret.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'crypto_random_shared_secret', [_kind])))); @override Future generateKeyPair() async => KeyPair.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'crypto_generate_key_pair', [_kind])))); @override Future generateHash(Uint8List data) async => HashDigest.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'crypto_generate_hash', [_kind, base64UrlNoPadEncode(data)])))); @override Future validateKeyPair(PublicKey key, SecretKey secret) => _wrapApiPromise(js_util.callMethod(wasm, 'crypto_validate_key_pair', [_kind, jsonEncode(key), jsonEncode(secret)])); @override Future validateHash(Uint8List data, HashDigest hash) => _wrapApiPromise(js_util.callMethod(wasm, 'crypto_validate_hash', [_kind, base64UrlNoPadEncode(data), jsonEncode(hash)])); @override Future distance(CryptoKey key1, CryptoKey key2) async => CryptoKeyDistance.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'crypto_distance', [_kind, jsonEncode(key1), jsonEncode(key2)])))); @override Future sign( PublicKey key, SecretKey secret, Uint8List data) async => Signature.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod(wasm, 'crypto_sign', [ _kind, jsonEncode(key), jsonEncode(secret), base64UrlNoPadEncode(data) ])))); @override Future verify(PublicKey key, Uint8List data, Signature signature) => _wrapApiPromise(js_util.callMethod(wasm, 'crypto_verify', [ _kind, jsonEncode(key), base64UrlNoPadEncode(data), jsonEncode(signature), ])); @override Future aeadOverhead() => _wrapApiPromise( js_util.callMethod(wasm, 'crypto_aead_overhead', [_kind])); @override Future decryptAead(Uint8List body, Nonce nonce, SharedSecret sharedSecret, Uint8List? associatedData) async => base64UrlNoPadDecode( await _wrapApiPromise(js_util.callMethod(wasm, 'crypto_decrypt_aead', [ _kind, base64UrlNoPadEncode(body), jsonEncode(nonce), jsonEncode(sharedSecret), if (associatedData != null) base64UrlNoPadEncode(associatedData) else null ]))); @override Future encryptAead(Uint8List body, Nonce nonce, SharedSecret sharedSecret, Uint8List? associatedData) async => base64UrlNoPadDecode( await _wrapApiPromise(js_util.callMethod(wasm, 'crypto_encrypt_aead', [ _kind, base64UrlNoPadEncode(body), jsonEncode(nonce), jsonEncode(sharedSecret), if (associatedData != null) base64UrlNoPadEncode(associatedData) else null ]))); @override Future cryptNoAuth( Uint8List body, Nonce nonce, SharedSecret sharedSecret) async => base64UrlNoPadDecode(await _wrapApiPromise(js_util.callMethod( wasm, 'crypto_crypt_no_auth', [ _kind, base64UrlNoPadEncode(body), jsonEncode(nonce), jsonEncode(sharedSecret) ]))); } class _TDBT { _TDBT(this.id, this.tdbjs, this.js); int? id; final VeilidTableDBJS tdbjs; final VeilidJS js; void ensureValid() { if (id == null) { throw VeilidAPIExceptionNotInitialized(); } } void close() { if (id != null) { js_util.callMethod(wasm, 'release_table_db_transaction', [id!]); id = null; } } } // JS implementation of VeilidTableDBTransaction class VeilidTableDBTransactionJS extends VeilidTableDBTransaction { VeilidTableDBTransactionJS._(this._tdbt) { _finalizer.attach(this, _tdbt, detach: this); } final _TDBT _tdbt; static final Finalizer<_TDBT> _finalizer = Finalizer((tdbt) => tdbt.close()); @override bool isDone() => _tdbt.id == null; @override Future commit() async { _tdbt.ensureValid(); await _wrapApiPromise( js_util.callMethod(wasm, 'table_db_transaction_commit', [_tdbt.id!])); _tdbt.close(); } @override Future rollback() async { _tdbt.ensureValid(); await _wrapApiPromise( js_util.callMethod(wasm, 'table_db_transaction_rollback', [_tdbt.id!])); _tdbt.close(); } @override Future store(int col, Uint8List key, Uint8List value) async { _tdbt.ensureValid(); final encodedKey = base64UrlNoPadEncode(key); final encodedValue = base64UrlNoPadEncode(value); await _wrapApiPromise(js_util.callMethod(wasm, 'table_db_transaction_store', [_tdbt.id!, col, encodedKey, encodedValue])); } @override Future delete(int col, Uint8List key) async { _tdbt.ensureValid(); final encodedKey = base64UrlNoPadEncode(key); await _wrapApiPromise(js_util.callMethod( wasm, 'table_db_transaction_delete', [_tdbt.id!, col, encodedKey])); } } class _TDB { _TDB(int this.id, this.js); int? id; final VeilidJS js; void ensureValid() { if (id == null) { throw VeilidAPIExceptionNotInitialized(); } } void close() { if (id != null) { js_util.callMethod(wasm, 'release_table_db', [id!]); id = null; } } } // JS implementation of VeilidTableDB class VeilidTableDBJS extends VeilidTableDB { VeilidTableDBJS._(this._tdb) { _finalizer.attach(this, _tdb, detach: this); } final _TDB _tdb; static final Finalizer<_TDB> _finalizer = Finalizer((tdb) => tdb.close()); @override void close() { _tdb.close(); } @override int getColumnCount() { _tdb.ensureValid(); return js_util.callMethod(wasm, 'table_db_get_column_count', [_tdb.id!]); } @override Future> getKeys(int col) async { _tdb.ensureValid(); return jsonListConstructor(base64UrlNoPadDecodeDynamic)(jsonDecode( await js_util.callMethod(wasm, 'table_db_get_keys', [_tdb.id!, col]))); } @override VeilidTableDBTransaction transact() { _tdb.ensureValid(); 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) { _tdb.ensureValid(); 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 { _tdb.ensureValid(); final encodedKey = base64UrlNoPadEncode(key); final 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) { _tdb.ensureValid(); 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 extends Veilid { @override void initializeVeilidCore(Map platformConfigJson) { final platformConfigJsonString = jsonEncode(platformConfigJson); js_util .callMethod(wasm, 'initialize_veilid_core', [platformConfigJsonString]); } @override void changeLogLevel(String layer, VeilidConfigLogLevel logLevel) { final logLevelJsonString = jsonEncode(logLevel); js_util.callMethod(wasm, 'change_log_level', [layer, logLevelJsonString]); } @override Future> startupVeilidCore(VeilidConfig config) async { final streamController = StreamController(); updateCallback(String update) { final updateJson = jsonDecode(update); if (updateJson['kind'] == 'Shutdown') { streamController.close(); } else { final update = VeilidUpdate.fromJson(updateJson); streamController.add(update); } } await _wrapApiPromise(js_util.callMethod(wasm, 'startup_veilid_core', [js.allowInterop(updateCallback), jsonEncode(config)])); return streamController.stream; } @override Future getVeilidState() async => VeilidState.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'get_veilid_state', [])))); @override Future attach() => _wrapApiPromise(js_util.callMethod(wasm, 'attach', [])); @override Future detach() => _wrapApiPromise(js_util.callMethod(wasm, 'detach', [])); @override Future shutdownVeilidCore() => _wrapApiPromise( js_util.callMethod(wasm, 'shutdown_veilid_core', [])); @override List validCryptoKinds() { final vck = jsonDecode(js_util.callMethod(wasm, 'valid_crypto_kinds', [])) as List; return vck.map((v) => v as CryptoKind).toList(); } @override Future getCryptoSystem(CryptoKind kind) async { if (!validCryptoKinds().contains(kind)) { throw const VeilidAPIExceptionGeneric('unsupported cryptosystem'); } return VeilidCryptoSystemJS._(this, kind); } @override Future bestCryptoSystem() async => VeilidCryptoSystemJS._( this, js_util.callMethod(wasm, 'best_crypto_kind', [])); @override Future> verifySignatures(List nodeIds, Uint8List data, List signatures) async => jsonListConstructor(TypedKey.fromJson)(jsonDecode( await _wrapApiPromise(js_util.callMethod(wasm, 'verify_signatures', [ jsonEncode(nodeIds), base64UrlNoPadEncode(data), jsonEncode(signatures) ])))); @override Future> generateSignatures( Uint8List data, List keyPairs) async => jsonListConstructor(TypedSignature.fromJson)(jsonDecode( await _wrapApiPromise(js_util.callMethod(wasm, 'generate_signatures', [base64UrlNoPadEncode(data), jsonEncode(keyPairs)])))); @override Future generateKeyPair(CryptoKind kind) async => TypedKeyPair.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'generate_key_pair', [kind])))); @override Future routingContext() async { final var id = await _wrapApiPromise(js_util.callMethod(wasm, 'routing_context', [])); return VeilidRoutingContextJS._(_Ctx(id, this)); } @override Future newPrivateRoute() async => RouteBlob.fromJson(jsonDecode(await _wrapApiPromise( js_util.callMethod(wasm, 'new_private_route', [])))); @override Future newCustomPrivateRoute( Stability stability, Sequencing sequencing) async { final stabilityString = jsonEncode(stability); final sequencingString = jsonEncode(sequencing); return RouteBlob.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod( wasm, 'new_private_route', [stabilityString, sequencingString])))); } @override Future importRemotePrivateRoute(Uint8List blob) { final encodedBlob = base64UrlNoPadEncode(blob); return _wrapApiPromise( js_util.callMethod(wasm, 'import_remote_private_route', [encodedBlob])); } @override Future releasePrivateRoute(String key) => _wrapApiPromise( js_util.callMethod(wasm, 'release_private_route', [key])); @override Future appCallReply(String callId, Uint8List message) { final encodedMessage = base64UrlNoPadEncode(message); return _wrapApiPromise( js_util.callMethod(wasm, 'app_call_reply', [callId, encodedMessage])); } @override Future openTableDB(String name, int columnCount) async { final id = await _wrapApiPromise( js_util.callMethod(wasm, 'open_table_db', [name, columnCount])); return VeilidTableDBJS._(_TDB(id, this)); } @override Future deleteTableDB(String name) => _wrapApiPromise(js_util.callMethod(wasm, 'delete_table_db', [name])); @override Timestamp now() => Timestamp.fromString(js_util.callMethod(wasm, 'now', [])); @override Future debug(String command) async => await _wrapApiPromise(js_util.callMethod(wasm, 'debug', [command])); @override String veilidVersionString() => 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']); } }