blob: 7d833ab4fe4867867696bbfd082a4ec1f0a556aa [file] [log] [blame]
// Copyright (c) 2020, 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.
/// A library used to spawn the Dart Developer Service, used to communicate
/// with a Dart VM Service instance.
library dds;
import 'dart:async';
import 'dart:io';
import 'src/dds_impl.dart';
/// An intermediary between a Dart VM service and its clients that offers
/// additional functionality on top of the standard VM service protocol.
///
/// See the [Dart Development Service Protocol](https://github.com/dart-lang/sdk/blob/master/pkg/dds/dds_protocol.md)
/// for details.
abstract class DartDevelopmentService {
/// Creates a [DartDevelopmentService] instance which will communicate with a
/// VM service. Requires the target VM service to have no other connected
/// clients.
///
/// [remoteVmServiceUri] is the address of the VM service that this
/// development service will communicate with.
///
/// If provided, [serviceUri] will determine the address and port of the
/// spawned Dart Development Service. The format of [serviceUri] must be
/// consistent with the protocol determined by [ipv6].
///
/// [enableAuthCodes] controls whether or not an authentication code must
/// be provided by clients when communicating with this instance of
/// [DartDevelopmentService]. Authentication codes take the form of a base64
/// encoded string provided as the first element of the DDS path and is meant
/// to make it more difficult for unintended clients to connect to this
/// service. Authentication codes are enabled by default.
///
/// [ipv6] controls whether or not DDS is served via IPv6. IPv4 is enabled by
/// default.
///
/// If [enablesServicePortFallback] is enabled, DDS will attempt to bind to any
/// available port if the specified port is unavailable.
static Future<DartDevelopmentService> startDartDevelopmentService(
Uri remoteVmServiceUri, {
Uri? serviceUri,
bool enableAuthCodes = true,
bool ipv6 = false,
bool enableServicePortFallback = false,
List<String> cachedUserTags = const [],
DevToolsConfiguration? devToolsConfiguration,
bool logRequests = false,
}) async {
if (!remoteVmServiceUri.isScheme('http')) {
throw ArgumentError(
'remoteVmServiceUri must have an HTTP scheme. Actual: ${remoteVmServiceUri.scheme}',
);
}
if (serviceUri != null) {
if (!serviceUri.isScheme('http')) {
throw ArgumentError(
'serviceUri must have an HTTP scheme. Actual: ${serviceUri.scheme}',
);
}
// If provided an address to bind to, ensure it uses a protocol consistent
// with that used to spawn DDS.
final addresses = await InternetAddress.lookup(serviceUri.host);
try {
// Check to see if there's a valid address.
addresses.firstWhere(
(a) => (a.type ==
(ipv6 ? InternetAddressType.IPv6 : InternetAddressType.IPv4)),
);
} on StateError {
// Could not find a valid address.
throw ArgumentError(
"serviceUri '$serviceUri' is not an IPv${ipv6 ? "6" : "4"} address.",
);
}
}
final service = DartDevelopmentServiceImpl(
remoteVmServiceUri,
serviceUri,
enableAuthCodes,
cachedUserTags,
ipv6,
devToolsConfiguration,
logRequests,
enableServicePortFallback,
);
await service.startService();
return service;
}
DartDevelopmentService._();
/// Stop accepting requests after gracefully handling existing requests.
Future<void> shutdown();
/// Set to `true` if this instance of [DartDevelopmentService] requires an
/// authentication code to connect.
bool get authCodesEnabled;
/// Completes when this [DartDevelopmentService] has shut down.
Future<void> get done;
/// The HTTP [Uri] of the remote VM service instance that this service will
/// forward requests to.
Uri get remoteVmServiceUri;
/// The web socket [Uri] of the remote VM service instance that this service
/// will forward requests to.
///
/// Can be used with [WebSocket] to communicate directly with the VM service.
Uri get remoteVmServiceWsUri;
/// The [Uri] VM service clients can use to communicate with this
/// [DartDevelopmentService] via HTTP.
///
/// Returns `null` if the service is not running.
Uri? get uri;
/// The [Uri] VM service clients can use to communicate with this
/// [DartDevelopmentService] via server-sent events (SSE).
///
/// Returns `null` if the service is not running.
Uri? get sseUri;
/// The [Uri] VM service clients can use to communicate with this
/// [DartDevelopmentService] via a [WebSocket].
///
/// Returns `null` if the service is not running.
Uri? get wsUri;
/// The HTTP [Uri] of the hosted DevTools instance.
///
/// Returns `null` if DevTools is not running.
Uri? get devToolsUri;
/// Set to `true` if this instance of [DartDevelopmentService] is accepting
/// requests.
bool get isRunning;
/// The list of [UserTag]s used to determine which CPU samples are cached by
/// DDS.
List<String> get cachedUserTags;
/// The version of the DDS protocol supported by this [DartDevelopmentService]
/// instance.
static const String protocolVersion = '1.3';
}
class DartDevelopmentServiceException implements Exception {
/// Set when `DartDeveloperService.startDartDevelopmentService` is called and
/// the target VM service already has a Dart Developer Service instance
/// connected.
static const int existingDdsInstanceError = 1;
/// Set when the connection to the remote VM service terminates unexpectedly
/// during Dart Development Service startup.
static const int failedToStartError = 2;
/// Set when a connection error has occurred after startup.
static const int connectionError = 3;
factory DartDevelopmentServiceException.existingDdsInstance(String message) {
return DartDevelopmentServiceException._(existingDdsInstanceError, message);
}
factory DartDevelopmentServiceException.failedToStart() {
return DartDevelopmentServiceException._(
failedToStartError, 'Failed to start Dart Development Service');
}
factory DartDevelopmentServiceException.connectionIssue(String message) {
return DartDevelopmentServiceException._(connectionError, message);
}
DartDevelopmentServiceException._(this.errorCode, this.message);
@override
String toString() => 'DartDevelopmentServiceException: $message';
final int errorCode;
final String message;
}
class DevToolsConfiguration {
const DevToolsConfiguration({
required this.customBuildDirectoryPath,
this.enable = false,
});
final bool enable;
final Uri customBuildDirectoryPath;
}