Skip to content

Commit 943f4c6

Browse files
kenzieschmollCommit Queue
authored andcommitted
Add ConnectedAppService to DTD to store the connections to Dart and Flutter applications that DTD is aware of.
This service contains three methods and sends events over a stream: Methods: - `registerVmService(String uri, String secret)` - `unregisterVmService(String uri, String secret)` - `getVmServiceUris()` Stream events: - Sends `VmServiceRegistered` and `VmServiceUnregistered` events over the `ConnectedApp` stream. Bug: #60540 Change-Id: Ica90d1d14ea83b38f24b4229313effb628cf2d94 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427568 Reviewed-by: Jake Macdonald <[email protected]> Commit-Queue: Kenzie Davisson <[email protected]> Reviewed-by: Ben Konyi <[email protected]>
1 parent 626b913 commit 943f4c6

16 files changed

+960
-21
lines changed

pkg/dtd/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 2.6.0
2+
3+
- Add `ConnectedAppService` to store the connections to Dart and Flutter
4+
applications that DTD is aware of.
5+
- Log exceptions from invalid `streamNotify` events.
6+
17
## 2.5.1
28

39
- Widen the dependency on `unified_analytics` to include 8.0.0.

pkg/dtd/lib/dtd.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/// Support for communicating with the Dart Tooling Daemon.
66
library;
77

8+
export 'src/connected_app_service.dart';
89
export 'src/constants.dart';
910
export 'src/dart_tooling_daemon.dart';
1011
export 'src/file_system/types.dart';
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:json_rpc_2/json_rpc_2.dart';
6+
7+
import 'constants.dart';
8+
import 'dart_tooling_daemon.dart';
9+
import 'response_types.dart';
10+
import 'rpc_error_codes.dart';
11+
12+
/// Extension methods on the [DartToolingDaemon] that call the ConnectedApps
13+
/// service.
14+
extension ConnectedAppsExtension on DartToolingDaemon {
15+
/// Registers a new VM service connection at [uri].
16+
///
17+
/// This is a privileged RPC that requires a [secret] to be called
18+
/// successfully. This [secret] is generated by DTD at startup and provided to
19+
/// the spawner of DTD only so that it is the only DTD client that can call
20+
/// protected methods. If [secret] is invalid, an [RpcException] with error
21+
/// code [RpcErrorCodes.kPermissionDenied] will be thrown.
22+
///
23+
/// [exposedUri] is the URI for the VM service connection that has been
24+
/// exposed to the user/client machine if the backend VM service is running in
25+
/// a different location (for example, an editor running in the user's browser
26+
/// with the backend on a remote server).
27+
///
28+
/// Code that runs on the user/client machine (such as DevTools and DevTools
29+
/// extensions) should prefer this URI (if provided) whereas code that also
30+
/// runs on the backend (such as the debug adapter) should always use [uri].
31+
///
32+
/// [exposedUri] will be null or identical to [uri] in environments where
33+
/// there is no exposing to do (for example, an editor running locally on the
34+
/// same machine that the VM service is running).
35+
///
36+
/// [name] is the human-readable name for this VM service connection as
37+
/// defined by the DTD client calling this method (e.g. 'Flutter - Pixel 5').
38+
/// This is optional and may be null if the DTD client registering the VM
39+
/// service does not have any useful naming information to provide.
40+
///
41+
/// If a VM service connection cannot be established for [uri], an
42+
/// [RpcException] with error code [RpcErrorCodes.kConnectionFailed] will be
43+
/// thrown.
44+
///
45+
/// When this method is called, a
46+
/// [ConnectedAppServiceConstants.vmServiceRegistered] event will be sent over
47+
/// the [ConnectedAppServiceConstants.serviceName] stream.
48+
Future<Success> registerVmService({
49+
required String uri,
50+
required String secret,
51+
String? exposedUri,
52+
String? name,
53+
}) {
54+
return _callOnConnectedAppService<Success>(
55+
ConnectedAppServiceConstants.registerVmService,
56+
params: {
57+
EventParameters.uri: uri,
58+
EventParameters.secret: secret,
59+
if (exposedUri != null) EventParameters.exposedUri: exposedUri,
60+
if (name != null) EventParameters.name: name,
61+
},
62+
parseResponse: Success.fromDTDResponse,
63+
);
64+
}
65+
66+
/// Unregisters the VM service connection at [uri], if it exists.
67+
///
68+
/// If the VM service at [uri] was not already registered, this method does
69+
/// nothing.
70+
///
71+
/// This is a privileged RPC that requires a [secret] to be called
72+
/// successfully. This [secret] is generated by DTD at startup and provided to
73+
/// the spawner of DTD only so that it is the only DTD client that can call
74+
/// protected methods. If [secret] is invalid, an [RpcException] with error
75+
/// code [RpcErrorCodes.kPermissionDenied] will be thrown.
76+
///
77+
/// When this method is called, a
78+
/// [ConnectedAppServiceConstants.vmServiceUnregistered] event will be sent
79+
/// over the [ConnectedAppServiceConstants.serviceName] stream.
80+
Future<Success> unregisterVmService({
81+
required String uri,
82+
required String secret,
83+
}) {
84+
return _callOnConnectedAppService<Success>(
85+
ConnectedAppServiceConstants.unregisterVmService,
86+
params: {EventParameters.uri: uri, EventParameters.secret: secret},
87+
parseResponse: Success.fromDTDResponse,
88+
);
89+
}
90+
91+
/// Returns a list of VM service URIs for running applications in the context
92+
/// of this DTD instance.
93+
Future<StringListResponse> getVmServiceUris() {
94+
return _callOnConnectedAppService<StringListResponse>(
95+
ConnectedAppServiceConstants.getVmServiceUris,
96+
parseResponse: StringListResponse.fromDTDResponse,
97+
);
98+
}
99+
100+
/// The stream of VM service update events.
101+
///
102+
/// Events sent over this stream will be of kind 'VmServiceRegistered'
103+
/// or 'VmServiceUnregistered' and will contain the VM service URI they are
104+
/// associated with.
105+
///
106+
/// A listener added to this [Stream] should be immediately followed by a
107+
/// call to `streamListen(kConnectedAppServiceName)`, which will allow the
108+
/// listener to start receiving events on this stream. Any events received
109+
/// before the listener is added and `streamListen` is called will be dropeed.
110+
Stream<DTDEvent> onVmServiceUpdate() {
111+
return onEvent(ConnectedAppServiceConstants.serviceName);
112+
}
113+
114+
Future<T> _callOnConnectedAppService<T>(
115+
String methodName, {
116+
Map<String, Object> params = const {},
117+
required T Function(DTDResponse) parseResponse,
118+
}) async {
119+
final response = await call(
120+
ConnectedAppServiceConstants.serviceName,
121+
methodName,
122+
params: params,
123+
);
124+
return parseResponse(response);
125+
}
126+
}

pkg/dtd/lib/src/constants.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,45 @@ const int defaultGetProjectRootsDepth = 4;
1616

1717
/// Service name for the DTD-hosted unified analytics service.
1818
const String kUnifiedAnalyticsServiceName = 'UnifiedAnalytics';
19+
20+
/// Constants used as parameter names across various DTD APIs.
21+
extension EventParameters on Never {
22+
static const eventData = 'eventData';
23+
static const eventKind = 'eventKind';
24+
static const exposedUri = 'exposedUri';
25+
static const name = 'name';
26+
static const secret = 'secret';
27+
static const streamId = 'streamId';
28+
static const timestamp = 'timestamp';
29+
static const uri = 'uri';
30+
}
31+
32+
/// Constants used by the DTD-hosted connected app service.
33+
extension ConnectedAppServiceConstants on Never {
34+
/// Service name for the DTD-hosted connected app service.
35+
///
36+
/// This is the same name used for the stream id that the connected app
37+
/// service sends VM service registered and unregistered events over.
38+
static const serviceName = 'ConnectedApp';
39+
40+
/// Service method name for the method that returns a list of VM service URIs
41+
/// for running applications in the context of a DTD instance.
42+
static const getVmServiceUris = 'getVmServiceUris';
43+
44+
/// Service method name for the method that registers a new VM service
45+
/// connection.
46+
static const registerVmService = 'registerVmService';
47+
48+
/// Service method name for the method that unregisters a VM service
49+
/// connection.
50+
static const unregisterVmService = 'unregisterVmService';
51+
52+
/// Event kind for the event that is sent over the [serviceName] stream when a
53+
/// new VM service is registered.
54+
static const vmServiceRegistered = 'VmServiceRegistered';
55+
56+
/// Event kind for the event that is sent over the [serviceName] stream when a
57+
/// VM service is unregistered, which happens automatically when the VM
58+
/// service shuts down.
59+
static const vmServiceUnregistered = 'VmServiceUnregistered';
60+
}

pkg/dtd/lib/src/dart_tooling_daemon.dart

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ typedef DTDServiceCallback = Future<Map<String, Object?>> Function(
1515
Parameters params,
1616
);
1717

18+
// TODO(kenz): replace all raw string values with constants from
19+
// `constants.dart` that can be shared with the dtd_impl package.
20+
1821
// TODO(danchevalier): add a serviceMethodIsAvailable experience. it will listen
1922
// to a stream that announces servicemethods getting registered and
2023
// unregistered. The state can then be presented as a listenable so that clients
@@ -31,19 +34,24 @@ class DartToolingDaemon {
3134
DartToolingDaemon.fromStreamChannel(StreamChannel<String> streamChannel)
3235
: _clientPeer = Peer(streamChannel) {
3336
_clientPeer.registerMethod('streamNotify', (Parameters params) {
34-
final streamId = params['streamId'].asString;
35-
final eventKind = params['eventKind'].asString;
36-
final eventData = params['eventData'].asMap as Map<String, Object?>;
37-
final timestamp = params['timestamp'].asInt;
38-
39-
_subscribedStreamControllers[streamId]?.add(
40-
DTDEvent(
41-
streamId,
42-
eventKind,
43-
eventData,
44-
timestamp,
45-
),
46-
);
37+
try {
38+
final streamId = params[EventParameters.streamId].asString;
39+
final eventKind = params[EventParameters.eventKind].asString;
40+
final eventData =
41+
params[EventParameters.eventData].asMap as Map<String, Object?>;
42+
final timestamp = params[EventParameters.timestamp].asInt;
43+
44+
_subscribedStreamControllers[streamId]?.add(
45+
DTDEvent(
46+
streamId,
47+
eventKind,
48+
eventData,
49+
timestamp,
50+
),
51+
);
52+
} catch (e) {
53+
print('Error while handling streamNotify event: $e');
54+
}
4755
});
4856

4957
_done = _clientPeer.listen();

pkg/dtd/lib/src/rpc_error_codes.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ abstract class RpcErrorCodes {
4242
// static const kFileSystemDoesNotExist = 1002;
4343
// static const kFileDoesNotExist = 1003;
4444

45+
static const kConnectionFailed = 150;
46+
4547
static const errorMessages = {
4648
kStreamAlreadySubscribed: 'Stream already subscribed',
4749
kStreamNotSubscribed: 'Stream not subscribed',
@@ -55,5 +57,7 @@ abstract class RpcErrorCodes {
5557
kPermissionDenied: 'Permission denied',
5658
kExpectsUriParamWithFileScheme: 'File scheme expected on uri',
5759
kUnknownUriScheme: 'URI scheme is not supported',
60+
// Custom error codes.
61+
kConnectionFailed: 'Connection failed',
5862
};
5963
}

pkg/dtd/pubspec.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: dtd
2-
version: 2.5.1
2+
version: 2.6.0
33
description: A package for communicating with the Dart Tooling Daemon.
44
repository: https://github.com/dart-lang/sdk/tree/main/pkg/dtd
55

@@ -19,6 +19,8 @@ dependencies:
1919
# best practice for packages is to specify their compatible version ranges.
2020
# See also https://dart.dev/tools/pub/dependencies.
2121
dev_dependencies:
22+
async: any
2223
dart_flutter_team_lints: any
2324
path: any
2425
test: any
26+
test_process: any

0 commit comments

Comments
 (0)