Open redDwarf03 opened 3 weeks ago
Try this for now
import 'dart:async';
import 'dart:js_interop';
import 'package:web/web.dart';
class MessagePortStreamChannel {
MessagePortStreamChannel({required this.port}) {
_onReceiveMessageSubscription = port.onMessage.listen((message) {
_in.add(message.data as String);
});
_onPostMessageSubscription = _out.stream.listen(port.postMessage);
}
final MessagePort port;
final _in = StreamController<String>(sync: true);
final _out = StreamController<JSAny?>(sync: true);
late final StreamSubscription<MessageEvent> _onReceiveMessageSubscription;
late final StreamSubscription<JSAny?> _onPostMessageSubscription;
Future<void> dispose() async {
await _onReceiveMessageSubscription.cancel();
await _onPostMessageSubscription.cancel();
await _in.close();
await _out.close();
}
}
extension on MessagePort {
Stream<MessageEvent> get onMessage =>
EventStreamProviders.messageEvent.forTarget(this);
}
We can add helpers here!
With your example, i don't understand now how to call the class
class MessageChannelArchethicDappClient extends AWCJsonRPCClient
implements ArchethicDAppClient {
MessageChannelArchethicDappClient({
required super.origin,
}) : super(
channelBuilder: () async {
if (awcAvailable != true) throw Failure.connectivity;
return MessagePortStreamChannel(
port: await asyncAWC,
);
},
disposeChannel: (StreamChannel<String> channel) async {
await (channel as MessagePortStreamChannel).dispose();
},
);
static bool get isAvailable => kIsWeb && awcAvailable == true;
}
The return type 'MessagePortStreamChannel' isn't a 'Future<StreamChannel<String>>', as required by the closure's context.dart[return_of_invalid_type_from_closure](https://dart.dev/diagnostics/return_of_invalid_type_from_closure)
For info, awc is
@JS()
library awc;
import 'dart:async';
import 'dart:developer';
import 'dart:js_interop';
import 'package:web/web.dart';
external MessagePort? get awc;
external bool? get awcAvailable;
@JS('onAWCReady')
external set onAWCReady(void Function(MessagePort awc) f);
Future<MessagePort> get asyncAWC async {
if (awc != null) return awc!;
log('Wait for awc');
final awcReadyCompleter = Completer<MessagePort>();
if (awc != null) awcReadyCompleter.complete(awc!);
onAWCReady = (awc) {
log('AWC ready !');
};
return awcReadyCompleter.future;
}
You might have to translate the StreamChannel from JSAny
or similar. I'm not sure...
Finally, it works with
import 'dart:async';
import 'dart:js_interop';
import 'package:archethic_wallet_client/archethic_wallet_client.dart';
import 'package:archethic_wallet_client/src/transport/common/awc_json_rpc_client.dart';
import 'package:archethic_wallet_client/src/transport/message_channel/message_channel.js.dart';
import 'package:flutter/foundation.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:web/web.dart';
class MessageChannelArchethicDappClient extends AWCJsonRPCClient
implements ArchethicDAppClient {
MessageChannelArchethicDappClient({
required super.origin,
}) : super(
channelBuilder: () async {
if (awcAvailable != true) throw Failure.connectivity;
return MessagePortStreamChannel(
port: await asyncAWC,
);
},
disposeChannel: (StreamChannel<String> channel) async {
await (channel as MessagePortStreamChannel).dispose();
},
);
static bool get isAvailable => kIsWeb && awcAvailable == true;
}
class MessagePortStreamChannel
with StreamChannelMixin<String>
implements StreamChannel<String> {
MessagePortStreamChannel({required this.port}) {
_onReceiveMessageSubscription = port.onMessage.listen((message) {
_in.add(message.data! as String);
});
_onPostMessageSubscription = _out.stream.listen((event) {
port.postMessage(event as JSAny?);
});
}
final MessagePort port;
final _in = StreamController<String>(sync: true);
final _out = StreamController<String>(sync: true);
late final StreamSubscription<MessageEvent> _onReceiveMessageSubscription;
late final StreamSubscription<String> _onPostMessageSubscription;
Future<void> dispose() async {
await _onReceiveMessageSubscription.cancel();
await _onPostMessageSubscription.cancel();
await _in.close();
await _out.close();
}
@override
StreamSink<String> get sink => _out.sink;
@override
Stream<String> get stream => _in.stream;
}
extension on MessagePort {
Stream<MessageEvent> get onMessage =>
EventStreamProviders.messageEvent.forTarget(this);
}
hello @kevmoo
I couldn't generate Chrome extension with my code in flutter 3.22 I read https://dart.dev/interop/js-interop/mock but that's not help me :/ Any idea ?
flutter build web --web-renderer html --csp
@JS()
library awc;
import 'dart:async';
import 'dart:developer';
import 'dart:js_interop';
import 'package:web/web.dart';
@JS()
external MessagePort? get awc;
@JS()
external bool? get awcAvailable;
@JS('onAWCReady')
external set onAWCReady(void Function(MessagePort awc) f);
Future<MessagePort> get asyncAWC async {
if (awc != null) {
return awc!;
}
log('Wait for awc');
final awcReadyCompleter = Completer<MessagePort>();
onAWCReady = (port) {
awcReadyCompleter.complete(port);
log('AWC ready !');
};
// Handle potential timeout or error (optional)
await Future.delayed(const Duration(seconds: 5), () {
if (!awcReadyCompleter.isCompleted) {
awcReadyCompleter.completeError(Exception('Timeout waiting for awc'));
}
});
return awcReadyCompleter.future;
}
Target dart2js failed: ProcessException: Process exited abnormally with exit code 1:
../archethic-wallet-client-dart/lib/src/transport/message_channel/message_channel.js.dart:16:14:
Error: External JS interop member contains an invalid type: 'void Function(MessagePort)'.
external set onAWCReady(void Function(MessagePort awc) f);
Other errors with other part of my code: initial code with js.dart (this code worked before flutter 3.22)
// ignore_for_file: avoid_setters_without_getters
@JS()
library awc;
import 'dart:async';
import 'package:js/js.dart';
@JS('archethic')
external ArchethicJS? get archethic;
@JS()
class ArchethicJS {
@JS('streamChannel')
external AWCStreamChannelJS? get streamChannel;
}
@JS()
class AWCStreamChannelJS {
@JS('state')
external AWCStreamChannelState get state;
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('connect')
external Object connect();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('close')
external Object close();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('send')
external Object send(String data);
@JS('onReceive')
external set onReceive(Future<void> Function(String data) callback);
@JS('onReady')
external set onReady(Future<void> Function() callback);
@JS('onClose')
external set onClose(Future<void> Function(String reason) callback);
}
enum AWCStreamChannelState {
connecting,
open,
closing,
closed,
}
to my new code with js_interop
// ignore_for_file: avoid_setters_without_getters
@JS()
library awc;
import 'dart:js_interop';
extension type ArchethicJS._(JSObject _) implements JSObject {
@JS('streamChannel')
external AWCStreamChannelJS? get streamChannel;
}
@JS('archethic')
external ArchethicJS? get archethic;
extension type AWCStreamChannelJS._(JSObject _) implements JSObject {
@JS('state')
external AWCStreamChannelState get state;
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('connect')
external JSObject connect();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('close')
external JSObject close();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('send')
external JSObject send(JSString data);
@JS('onReceive')
external set onReceive(void Function(JSString data) callback);
@JS('onReady')
external set onReady(void Function() callback);
@JS('onClose')
external set onClose(void Function(JSString reason) callback);
}
enum AWCStreamChannelState {
connecting,
open,
closing,
closed,
}
i have these error too.
^
../archethic-wallet-client-dart/lib/src/transport/webbrowser_extension/webbrowser_extension.js.dart:18:38:
Error: External JS interop member contains an invalid type: 'AWCStreamChannelState'.
- 'AWCStreamChannelState' is from 'package:archethic_wallet_client/src/transport/webbrowser_extension/webbrowser_extension.js.dart'
('../archethic-wallet-client-dart/lib/src/transport/webbrowser_extension/webbrowser_extension.js.dart').
external AWCStreamChannelState get state;
^
../archethic-wallet-client-dart/lib/src/transport/webbrowser_extension/webbrowser_extension.js.dart:36:16:
Error: External JS interop member contains an invalid type: 'void Function(JSString)'.
external set onReceive(void Function(JSString data) callback);
^
../archethic-wallet-client-dart/lib/src/transport/webbrowser_extension/webbrowser_extension.js.dart:39:16:
Error: External JS interop member contains an invalid type: 'void Function()'.
external set onReady(void Function() callback);
^
../archethic-wallet-client-dart/lib/src/transport/webbrowser_extension/webbrowser_extension.js.dart:42:16:
Error: External JS interop member contains an invalid type: 'void Function(JSString)'.
external set onClose(void Function(JSString reason) callback);
@srujzs ?
to be confirmed but to help community with js_interop, i share a solution i think
// ignore_for_file: avoid_setters_without_getters
@JS()
library awc;
import 'dart:js_interop';
extension type ArchethicJS._(JSObject _) implements JSObject {
@JS('streamChannel')
external AWCStreamChannelJS? get streamChannel;
}
@JS('archethic')
external ArchethicJS? get archethic;
extension type AWCStreamChannelJS._(JSObject _) implements JSObject {
@JS('state')
external AWCStreamChannelState get state;
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('connect')
external JSObject connect();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('close')
external JSObject close();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
@JS('send')
external JSObject send(JSString data);
@JS('onReceive')
external set onReceive(JSFunction callback);
@JS('onReady')
external set onReady(JSFunction callback);
@JS('onClose')
external set onClose(JSFunction callback);
}
@JS()
extension type AWCStreamChannelState._(JSObject _) implements JSObject {
external static AWCStreamChannelState get connecting;
external static AWCStreamChannelState get open;
external static AWCStreamChannelState get closing;
external static AWCStreamChannelState get closed;
}
FYI: you don't need to use @JS('whatever')
if the name is the same! You don't need the annotation at all!
Thx for the advice
A few drive-by comments:
JSFunction
.JSObject
when dealing with Promise
s by using JSPromise
. This allows you to convert them to a Future
that you can then await
using .toJS
.A few drive-by comments:
- The general errors around "invalid types" (which should be a little clearer with a newer version of the SDK) are related to https://dart.dev/interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs. Specifically, you can't pass arbitrary Dart functions to interop APIs, they need to be converted to a
JSFunction
.- You can be more specific that
JSObject
when dealing withPromise
s by usingJSPromise
. This allows you to convert them to aFuture
that you can thenawait
using.toJS
.
Thank you for advices. I change types:
// ignore_for_file: avoid_setters_without_getters
@JS()
library awc;
import 'dart:js_interop';
extension type ArchethicJS._(JSObject _) implements JSObject {
@JS('streamChannel')
external AWCStreamChannelJS? get streamChannel;
}
@JS('archethic')
external ArchethicJS? get archethic;
extension type AWCStreamChannelJS._(JSObject _) implements JSObject {
external AWCStreamChannelState get state;
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
external JSPromise connect();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
external JSPromise close();
/// This returns a promise.
/// You must use `promiseTofuture` to call this from Dart code.
external JSPromise send(JSString data);
external set onReceive(JSFunction callback);
external set onReady(JSFunction callback);
external set onClose(JSFunction callback);
}
@JS()
extension type AWCStreamChannelState._(JSObject _) implements JSObject {
external static AWCStreamChannelState get connecting;
external static AWCStreamChannelState get open;
external static AWCStreamChannelState get closing;
external static AWCStreamChannelState get closed;
}
but i met execution error on streamChannel.send(event as JSString);
:
import 'dart:async';
import 'dart:developer';
import 'dart:js_interop';
import 'package:archethic_wallet_client/archethic_wallet_client.dart';
import 'package:archethic_wallet_client/src/transport/common/awc_json_rpc_client.dart';
import 'package:archethic_wallet_client/src/transport/webbrowser_extension/webbrowser_extension.js.dart';
import 'package:stream_channel/stream_channel.dart';
class WebBrowserExtensionDappClient extends AWCJsonRPCClient
implements ArchethicDAppClient {
WebBrowserExtensionDappClient({
required super.origin,
}) : super(
channelBuilder: () async {
if (archethic?.streamChannel == null) {
throw Failure.connectivity;
}
final streamChannel = WebBrowserExtensionStreamChannel(
streamChannel: archethic!.streamChannel!,
);
await streamChannel.connect();
return streamChannel;
},
disposeChannel: (channel) async {
await (channel as WebBrowserExtensionStreamChannel).dispose();
},
);
static bool get isAvailable => archethic?.streamChannel != null;
}
class WebBrowserExtensionStreamChannel
with StreamChannelMixin<String>
implements StreamChannel<String> {
WebBrowserExtensionStreamChannel({required this.streamChannel}) {
streamChannel.onReceive = (message) async {
log('[WBE] command received $message');
_in.add(message.toString());
log('[WBE] command received Done');
}.toJS;
_onPostMessageSubscription = _out.stream.listen((event) {
log('[WBE] send command $event');
streamChannel.send(event as JSString);
log('[WBE] send command Done');
});
streamChannel.onClose = (reason) async {
await dispose();
}.toJS;
}
Future<void> connect() async => streamChannel.connect();
final AWCStreamChannelJS streamChannel;
final _in = StreamController<String>(sync: true);
final _out = StreamController<String>(sync: true);
late final StreamSubscription<String> _onPostMessageSubscription;
Future<void> dispose() async {
await _onPostMessageSubscription.cancel();
await _in.close();
await _out.close();
}
@override
StreamSink<String> get sink => _out.sink;
@override
Stream<String> get stream => _in.stream;
}
I'm guessing you're running with dart2wasm. Avoid casting String
to JSString
. Prefer using .toJS
instead to convert the String
instead.
If you're using 3.5.0-1XX.X.beta
, you can enable the lint invalid_runtime_check_with_js_interop_types
to catch invalid casts like this.
Hello
I have a class
When i use
import 'package:web/web.dart';
,onMessage
method isn't defined for the type 'MessagePort'. but in the class we haveexternal EventHandler get onmessage;
i don't understand something to migrate my class to web package.
thx