blob: 5c24eb6910a9056d362d9de4211f4fa7570d768e [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:stream_channel/stream_channel.dart';
import 'package:web_socket_channel/io.dart';
import 'src/exceptions.dart';
import 'src/flag.dart';
import 'src/isolate.dart';
import 'src/service_version.dart';
import 'src/stream_manager.dart';
import 'src/utils.dart';
import 'src/v1_compatibility.dart';
import 'src/vm.dart';
export 'src/bound_field.dart' hide newVMBoundField;
export 'src/breakpoint.dart' hide newVMBreakpoint;
export 'src/class.dart' hide newVMClassRef;
export 'src/code.dart' hide newVMCodeRef;
export 'src/context.dart' hide newVMContextRef;
export 'src/error.dart' hide newVMError, newVMErrorRef;
export 'src/exceptions.dart';
export 'src/field.dart' hide newVMFieldRef;
export 'src/flag.dart' hide newVMFlagList;
export 'src/frame.dart' hide newVMFrame;
export 'src/function.dart' hide newVMFunctionRef;
export 'src/instance.dart' hide newVMInstanceRef, newVMInstance,
newVMTypeLikeInstanceRef, newVMTypeInstanceRef, newVMInstanceRefOrSentinel;
export 'src/isolate.dart' hide newVMIsolateRef;
export 'src/library.dart' hide newVMLibraryRef;
export 'src/message.dart' hide newVMMessage;
export 'src/object.dart';
export 'src/pause_event.dart' hide newVMPauseEvent;
export 'src/script.dart' hide newVMScriptRef, newVMScriptToken,
newVMScriptTokenFromPosition;
export 'src/sentinel.dart' hide newVMSentinel;
export 'src/service_version.dart' hide newVMServiceVersion;
export 'src/source_location.dart' hide newVMSourceLocation,
newVMSourceLocationFromPosition;
export 'src/source_report.dart' hide newSourceReport;
export 'src/stack.dart' hide newVMStack;
export 'src/type_arguments.dart' hide newVMTypeArgumentsRef;
export 'src/unresolved_source_location.dart' hide newVMUnresolvedSourceLocation;
export 'src/vm.dart' hide newVM;
/// A client for the [Dart VM service protocol][service api].
///
/// [service api]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md
///
/// Connect to a VM service endpoint using [connect], and use [getVM] to load
/// information about the VM itself.
///
/// The client supports VM service versions 1.x (which first shipped with Dart
/// 1.11), 2.x (which first shipped with Dart 1.12), and 3.x (which first
/// shipped with Dart 1.13). Some functionality may be unavailable in older VM
/// service versions; those places will be clearly documented. You can check the
/// version of the VM service you're connected to using [getVersion].
///
/// Because it takes an extra RPC call to verify compatibility with the protocol
/// version, the client doesn't do so by default. Users who want to be sure
/// they're talking to a supported protocol version can call [validateVersion].
class VMServiceClient {
/// The underlying JSON-RPC peer used to communicate with the VM service.
final rpc.Peer _peer;
/// The streams shared among the entire service protocol client.
final StreamManager _streams;
/// A broadcast stream that emits every isolate as it starts.
Stream<VMIsolateRef> get onIsolateStart => _onIsolateStart;
Stream<VMIsolateRef> _onIsolateStart;
/// A broadcast stream that emits every isolate as it becomes runnable.
///
/// These isolates are guaranteed to return a [VMRunnableIsolate] from
/// [VMIsolateRef.load].
///
/// This is only supported on the VM service protocol version 3.0 and greater.
Stream<VMIsolateRef> get onIsolateRunnable => _onIsolateRunnable;
Stream<VMIsolateRef> _onIsolateRunnable;
/// A future that fires when the underlying connection has been closed.
///
/// Any connection-level errors will also be emitted through this future.
final Future done;
/// Connects to the VM service protocol at [url].
///
/// [url] may be a `ws://` or a `http://` URL. If it's `ws://`, it's
/// interpreted as the URL to connect to directly. If it's `http://`, it's
/// interpreted as the URL for the Dart observatory, and the corresponding
/// WebSocket URL is determined based on that. It may be either a [String] or
/// a [Uri].
///
/// If this encounters a connection error, [done] will complete with a
/// [WebSocketChannelException].
factory VMServiceClient.connect(url) {
if (url is! Uri && url is! String) {
throw new ArgumentError.value(url, "url", "must be a String or a Uri");
}
var uri = url is String ? Uri.parse(url) : url;
if (uri.scheme == 'http') uri = uri.replace(scheme: 'ws', path: '/ws');
// TODO(nweiz): Just use [WebSocketChannel.connect] when cross-platform
// libraries work.
return new VMServiceClient(new IOWebSocketChannel.connect(uri).cast());
}
/// Creates a client that reads incoming messages from a [channel] which
/// contains JSON-encoded [String] instances.
///
/// This is useful when using the client over a pre-existing connection. To
/// establish a connection from scratch, use [VMServiceClient.connect].
factory VMServiceClient(StreamChannel<String> channel) =>
new VMServiceClient.withoutJson(jsonDocument.bind(channel));
/// Creates a client that reads incoming messages from a [channel] which
/// contains decoded JSON maps and lists.
///
/// This is useful when using the client over a pre-existing connection. To
/// establish a connection from scratch, use [VMServiceClient.connect].
factory VMServiceClient.withoutJson(StreamChannel channel) =>
new VMServiceClient._(new rpc.Peer.withoutJson(
channel.transformStream(v1CompatibilityTransformer)));
VMServiceClient._(rpc.Peer peer)
: _peer = peer,
_streams = new StreamManager(peer),
done = peer.listen() {
_onIsolateStart = transform(_streams.isolate, (json, sink) {
if (json["kind"] != "IsolateStart") return;
sink.add(newVMIsolateRef(_peer, _streams, json["isolate"]));
});
_onIsolateRunnable = transform(_streams.isolate, (json, sink) {
if (json["kind"] != "IsolateRunnable") return;
sink.add(newVMIsolateRef(_peer, _streams, json["isolate"]));
});
}
/// Checks the VM service protocol version and throws a
/// [VMUnsupportedVersionException] if it's not a supported version.
///
/// Because it's possible the VM service protocol doesn't speak JSON-RPC 2.0
/// at all, by default this will also throw a [VMUnsupportedVersionException]
/// if a reply isn't received within two seconds. This timeout can be
/// controlled with [timeout], or `null` can be passed to use no timeout.
Future validateVersion({Duration timeout: const Duration(seconds: 2)}) {
var future = _peer.sendRequest("getVersion", {}).then((json) {
var version;
try {
version = newVMServiceVersion(json);
} catch (_) {
throw new VMUnsupportedVersionException();
}
if (version.major < 2 || version.major > 3) {
throw new VMUnsupportedVersionException(version);
}
});
if (timeout == null) return future;
return future.timeout(timeout, onTimeout: () {
throw new VMUnsupportedVersionException();
});
}
/// Closes the underlying connection to the VM service.
///
/// Returns a [Future] that fires once the connection has been closed.
Future close() => _peer.close();
/// Returns a list of flags that were passed to the VM.
///
/// As of VM service version 3.0, this only includes VM-internal flags.
Future<List<VMFlag>> getFlags() async =>
newVMFlagList(await _peer.sendRequest("getFlagList", {}));
/// Returns the version of the VM service protocol that this client is
/// communicating with.
///
/// Note that this is distinct from the version of Dart, which is accessible
/// via [VM.version]. Note also that this doesn't necessarily accurately
/// reflect the available APIs; for example, VM service versions 3.1, 3.2, and
/// 3.3 all reported themseves as version 3.0.
Future<VMServiceVersion> getVersion() async =>
newVMServiceVersion(await _peer.sendRequest("getVersion", {}));
/// Returns information about the Dart VM.
Future<VM> getVM() async =>
newVM(_peer, _streams, await _peer.sendRequest("getVM", {}));
}