connectDebugger method
Connects to the VM Service at uri
and initializes debugging.
This method will be called by sub-classes when they are ready to start a debug session and may provide a URI given by the user (in the case of attach) or from something like a vm-service-info file or Flutter app.debugPort message.
The URI protocol will be changed to ws/wss but otherwise not normalized. The caller should handle any other normalisation (such as adding /ws to the end if required).
Implementation
Future<void> connectDebugger(Uri uri) async {
// Start up a DDS instance for this VM.
if (enableDds) {
logger?.call('Starting a DDS instance for $uri');
try {
final dds = await startDds(uri, uriConverter());
_dds = dds;
uri = dds.wsUri!;
} on DartDevelopmentServiceException catch (e) {
// If there's already a DDS instance, then just continue. This is common
// when attaching, as the program may have already been run with a DDS
// instance.
if (e.errorCode ==
DartDevelopmentServiceException.existingDdsInstanceError) {
uri = vmServiceUriToWebSocket(uri);
} else {
rethrow;
}
}
} else {
uri = vmServiceUriToWebSocket(uri);
}
logger?.call('Connecting to debugger at $uri');
sendConsoleOutput('Connecting to VM Service at $uri');
final vmService = await _vmServiceConnectUri(uri.toString());
logger?.call('Connected to debugger at $uri!');
// Fetch DDS capabilities.
final supportedProtocols = await vmService.getSupportedProtocols();
final ddsProtocol = supportedProtocols.protocols
?.firstWhereOrNull((protocol) => protocol.protocolName == 'DDS');
if (ddsProtocol != null) {
_ddsCapabilities = _DdsCapabilities(
major: ddsProtocol.major ?? 0,
minor: ddsProtocol.minor ?? 0,
);
}
final supportsCustomStreams = _ddsCapabilities.supportsCustomStreams;
// Send debugger URI to the client.
sendDebuggerUris(uri);
this.vmService = vmService;
unawaited(vmService.onDone.then((_) => _handleVmServiceClosed()));
// Handlers must be wrapped to handle Service Disappeared errors if async
// code tries to call the VM Service after termination begins.
final wrap = _wrapHandlerWithErrorHandling;
_subscriptions.addAll([
vmService.onIsolateEvent.listen(wrap(handleIsolateEvent)),
vmService.onDebugEvent.listen(wrap(handleDebugEvent)),
vmService.onLoggingEvent.listen(wrap(handleLoggingEvent)),
vmService.onExtensionEvent.listen(wrap(handleExtensionEvent)),
vmService.onServiceEvent.listen(wrap(handleServiceEvent)),
if (supportsCustomStreams)
vmService.onEvent(toolEventStreamId).listen(wrap(handleToolEvent)),
if (_subscribeToOutputStreams) ...[
vmService.onStdoutEvent.listen(wrap(_handleStdoutEvent)),
vmService.onStderrEvent.listen(wrap(_handleStderrEvent)),
],
]);
await Future.wait([
vmService.streamListen(vm.EventStreams.kIsolate),
vmService.streamListen(vm.EventStreams.kDebug),
vmService.streamListen(vm.EventStreams.kLogging),
vmService.streamListen(vm.EventStreams.kExtension),
vmService.streamListen(vm.EventStreams.kService),
if (supportsCustomStreams) vmService.streamListen(toolEventStreamId),
if (_subscribeToOutputStreams) ...[
vmService.streamListen(vm.EventStreams.kStdout),
vmService.streamListen(vm.EventStreams.kStderr),
],
]);
final vmInfo = await vmService.getVM();
logger?.call('Connected to ${vmInfo.name} on ${vmInfo.operatingSystem}');
// Let the subclass do any existing setup once we have a connection.
await debuggerConnected(vmInfo);
await _withErrorHandling(
() => _configureExistingIsolates(vmService, vmInfo),
);
_debuggerInitializedCompleter.complete();
}