Fix JS value to Dart conversion when receiving from a web socket (dart-lang/web_socket_channel#298)
`MessageEvent` is a `package:web` type and `data` field is a JS value of type
`JSAny?`.
The receiving end of the `sink` is here:
https://github.com/dart-lang/sdk/blob/26107a319a7503deafee404e3462644a873e2920/pkg/vm_service/lib/src/vm_service.dart#L1795
This code currently does not expect to see JS objects, so passing a `JSAny?` to
the sink breaks it.
The fix should be in the generating end rather than the receiving end: `JSAny?`
is supposed to be an unboxed value, not a boxed Dart value. In an ideal world we
shouldn't be able to pass it as a Dart object. So we call `dartify` and convert
it to a Dart object.
This fixes DevTools when compiled to Wasm.
Thanks to @eyebrowsoffire and @mkustermann for help with debugging.
Co-authored-by: Kevin Moore <kevmoo@google.com>
diff --git a/pkgs/web_socket_channel/lib/html.dart b/pkgs/web_socket_channel/lib/html.dart
index 82edd2d..5446b67 100644
--- a/pkgs/web_socket_channel/lib/html.dart
+++ b/pkgs/web_socket_channel/lib/html.dart
@@ -127,13 +127,17 @@
}
void _innerListen(MessageEvent event) {
+ // Event data will be ArrayBuffer, Blob, or String.
final eventData = event.data;
- Object? data;
- if (eventData.typeofEquals('object') &&
+ final Object? data;
+ if (eventData.typeofEquals('string')) {
+ data = (eventData as JSString).toDart;
+ } else if (eventData.typeofEquals('object') &&
(eventData as JSObject).instanceOfString('ArrayBuffer')) {
data = (eventData as JSArrayBuffer).toDart.asUint8List();
} else {
- data = event.data;
+ // Blobs are passed directly.
+ data = eventData;
}
_controller.local.sink.add(data);
}
diff --git a/pkgs/web_socket_channel/test/html_test.dart b/pkgs/web_socket_channel/test/html_test.dart
index 9d91025..69f0cd9 100644
--- a/pkgs/web_socket_channel/test/html_test.dart
+++ b/pkgs/web_socket_channel/test/html_test.dart
@@ -10,12 +10,19 @@
import 'dart:typed_data';
import 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
import 'package:test/test.dart';
import 'package:web/helpers.dart' hide BinaryType;
import 'package:web_socket_channel/html.dart';
import 'package:web_socket_channel/src/web_helpers.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
+extension on StreamChannel {
+ /// Handles the Wasm case where the runtime type is actually [double] instead
+ /// of the JS case where its [int].
+ Future<int> get firstAsInt async => ((await stream.first) as num).toInt();
+}
+
void main() {
late int port;
setUpAll(() async {
@@ -35,7 +42,7 @@
}
''', stayAlive: true);
- port = await channel.stream.first as int;
+ port = await channel.firstAsInt;
});
test('communicates using an existing WebSocket', () async {
@@ -169,7 +176,7 @@
// TODO(nweiz): Make this channel use a port number that's guaranteed to be
// invalid.
final channel = HtmlWebSocketChannel.connect(
- 'ws://localhost:${await serverChannel.stream.first}');
+ 'ws://localhost:${await serverChannel.firstAsInt}');
expect(channel.ready, throwsA(isA<WebSocketChannelException>()));
expect(channel.stream.toList(), throwsA(isA<WebSocketChannelException>()));
});