blob: acb4b24b7907d02e2d6f1dc5074f020ca1d88ad3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. 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:convert';
import 'package:sse/server/sse_handler.dart';
class ClientManager {
ClientManager(this.requestNotificationPermissions);
/// Whether to immediately request notification permissions when a client connects.
/// Otherwise permission will be requested only with the first notification.
final bool requestNotificationPermissions;
final List<DevToolsClient> _clients = [];
List<DevToolsClient> get allClients => _clients.toList();
void acceptClient(SseConnection connection) {
final client = DevToolsClient(connection);
if (requestNotificationPermissions) {
client.enableNotifications();
}
_clients.add(client);
connection.sink.done.then((_) => _clients.remove(client));
}
/// Finds an active DevTools instance that is not already connecting to
/// a VM service that we can reuse (for example if a user stopped debugging
/// and it disconnected, then started debugging again, we want to reuse
/// the open DevTools window).
DevToolsClient findReusableClient() {
return _clients.firstWhere((c) => !c.hasConnection, orElse: () => null);
}
/// Finds a client that may already be connected to this VM Service.
DevToolsClient findExistingConnectedClient(Uri vmServiceUri) {
// Checking the whole URI will fail if DevTools converted it from HTTP to
// WS, so just check the host, port and first segment of path (token).
return _clients.firstWhere(
(c) =>
c.hasConnection && _areSameVmServices(c.vmServiceUri, vmServiceUri),
orElse: () => null);
}
bool _areSameVmServices(Uri uri1, Uri uri2) {
return uri1.host == uri2.host &&
uri1.port == uri2.port &&
uri1.pathSegments.isNotEmpty &&
uri2.pathSegments.isNotEmpty &&
uri1.pathSegments[0] == uri2.pathSegments[0];
}
}
class DevToolsClient {
DevToolsClient(this._connection) {
_connection.stream.listen((msg) {
try {
final request = jsonDecode(msg);
switch (request['method']) {
case 'connected':
_vmServiceUri = Uri.parse(request['params']['uri']);
return;
case 'disconnected':
_vmServiceUri = null;
return;
default:
print('Unknown request ${request['method']} from client');
}
} catch (e) {
print('Failed to handle API message from client:\n\n$msg\n\n$e');
}
});
}
Future<void> connectToVmService(Uri uri, bool notifyUser) async {
_connection.sink.add(jsonEncode({
'method': 'connectToVm',
'params': {
'uri': uri.toString(),
'notify': notifyUser,
},
}));
}
Future<void> notify() async {
_connection.sink.add(jsonEncode({
'method': 'notify',
}));
}
Future<void> enableNotifications() async {
_connection.sink.add(jsonEncode({
'method': 'enableNotifications',
}));
}
final SseConnection _connection;
Uri _vmServiceUri;
Uri get vmServiceUri => _vmServiceUri;
bool get hasConnection => _vmServiceUri != null;
}