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>()));
   });