Skip to content

Utility library for posting messages over web.MessageChannel #2349

@jonasfj

Description

@jonasfj

web.MessageChannel is the equivalent to isolate ports, but on web. It'd be nice if we could make a library like isolate_channel.dart called message_channel.dart or something, with utilities for making a stream channel over a MessageChannel.

I managed to do it with:

import 'dart:async';
import 'dart:js_interop';

import 'package:stream_channel/stream_channel.dart';
import 'package:web/web.dart' as web;

/// Returns a [StreamChannel] that communicates over [port].
///
/// The channel will dartify incoming messages and jsify outgoing messages.
StreamChannel<Object?> messagePortChannel(web.MessagePort port) {
  final input = StreamController<Object?>();
  final output = StreamController<Object?>();

  port.onmessage = (web.MessageEvent event) {
    input.sink.add(event.data.dartify());
  }.toJS;

  // This happens if message can't be deserialized on this side of the port
  // usually a browser bug, or something like SharedBuffers or other corner
  // cases; not likely to happen in our code.
  port.onmessageerror = (web.MessageEvent event) {
    // Close any transferred port (just in case)
    for (final p in event.ports.toDart) {
      p.close();
    }

    // Include origin in error message, if there is one.
    final originStr = event.origin.isNotEmpty
        ? ' (Origin: ${event.origin})'
        : '';

    input.sink.addError(
      FormatException(
        'MessagePort dropped a message due to '
        'deserialization failure$originStr',
      ),
    );
  }.toJS;

  port.start();

  output.stream.listen(
    (m) {
      port.postMessage(m.jsify());
    },
    onDone: () {
      unawaited(input.sink.close());
      port.close();
    },
  );

  return StreamChannel(input.stream, output.sink);
}

But it's not perfect, among other things when using it with package:json_rpc_2, RpcException can't be sent over it, I think because int becomes num/double. I stopped exploring.

Instead I ended up JSON encoding all the messages. And I honestly don't know which is most efficient anyways.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions