blob: f9b50724845f17633638f6f5557f6ec9e7e399f8 [file]
// Copyright 2020 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dtd/dtd.dart';
import 'package:meta/meta.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:vm_service/vm_service.dart';
import '../common.dart';
import '../deeplink/deeplink_manager.dart';
import '../devtools_api.dart';
import '../extensions/extension_enablement.dart';
import '../extensions/extension_manager.dart';
import '../service/service.dart';
import '../service_utils.dart';
import '../utils/file_utils.dart';
import 'devtools_store.dart';
import 'file_system.dart';
import 'flutter_store.dart';
part 'handlers/_app_size.dart';
part 'handlers/_deeplink.dart';
part 'handlers/_devtools_extensions.dart';
part 'handlers/_dtd.dart';
part 'handlers/_vm_service.dart';
part 'handlers/_preferences.dart';
part 'handlers/_release_notes.dart';
part 'handlers/_storage.dart';
part 'handlers/_survey.dart';
/// The DevTools server API.
///
/// This defines endpoints that serve all requests that come in over api/.
class ServerApi {
static const logsKey = 'logs';
static const errorKey = 'error';
/// Determines whether or not [request] is an API call.
static bool canHandle(shelf.Request request) {
return request.url.path.startsWith(apiPrefix);
}
/// Handles all requests.
///
/// To override an API call, pass in a subclass of [ServerApi].
static FutureOr<shelf.Response> handle(
shelf.Request request, {
required ExtensionsManager extensionsManager,
required DeeplinkManager deeplinkManager,
ServerApi? api,
DtdInfo? dtd,
}) {
api ??= ServerApi();
final queryParams = request.requestedUri.queryParameters;
switch (request.url.path) {
case apiNotifyForVmServiceConnection:
return VmServiceHandler.handleNotifyForVmServiceConnection(
api,
queryParams,
dtd,
);
// ----- Flutter Tool GA store. -----
case apiGetFlutterGAClientId:
return _encodeResponse(
LocalFileSystem.flutterStoreExists()
? _flutterStore.flutterClientId
: '',
api: api,
);
// ----- Preferences api. -----
case PreferencesApi.getPreferenceValue:
return _PreferencesApiHandler.getPreferenceValue(
api,
queryParams,
_devToolsStore,
);
case PreferencesApi.setPreferenceValue:
return _PreferencesApiHandler.setPreferenceValue(
api,
queryParams,
_devToolsStore,
);
// ----- DevTools survey api. -----
case SurveyApi.setActiveSurvey:
return _SurveyHandler.setActiveSurvey(api, queryParams, _devToolsStore);
case SurveyApi.getSurveyActionTaken:
return _SurveyHandler.getSurveyActionTaken(api, _devToolsStore);
case SurveyApi.setSurveyActionTaken:
return _SurveyHandler.setSurveyActionTaken(api, _devToolsStore);
case SurveyApi.getSurveyShownCount:
return _SurveyHandler.getSurveyShownCount(api, _devToolsStore);
case SurveyApi.incrementSurveyShownCount:
return _SurveyHandler.incrementSurveyShownCount(api, _devToolsStore);
// ----- Release notes api. -----
case ReleaseNotesApi.getLastReleaseNotesVersion:
return _ReleaseNotesHandler.getLastReleaseNotesVersion(
api,
_devToolsStore,
);
case ReleaseNotesApi.setLastReleaseNotesVersion:
return _ReleaseNotesHandler.setLastReleaseNotesVersion(
api,
queryParams,
_devToolsStore,
);
// ----- App size api. -----
case AppSizeApi.getBaseAppSizeFile:
return _AppSizeHandler.getBaseAppSizeFile(api, queryParams);
case AppSizeApi.getTestAppSizeFile:
return _AppSizeHandler.getTestAppSizeFile(api, queryParams);
// ----- Extensions api. -----
case ExtensionsApi.apiServeAvailableExtensions:
return _ExtensionsApiHandler.handleServeAvailableExtensions(
api,
queryParams,
extensionsManager,
dtd,
);
case ExtensionsApi.apiExtensionEnabledState:
return _ExtensionsApiHandler.handleExtensionEnabledState(
api,
queryParams,
);
// ----- deeplink api. -----
case DeeplinkApi.androidBuildVariants:
return _DeeplinkApiHandler.handleAndroidBuildVariants(
api,
queryParams,
deeplinkManager,
);
case DeeplinkApi.androidAppLinkSettings:
return _DeeplinkApiHandler.handleAndroidAppLinkSettings(
api,
queryParams,
deeplinkManager,
);
case DeeplinkApi.iosBuildOptions:
return _DeeplinkApiHandler.handleIosBuildOptions(
api,
queryParams,
deeplinkManager,
);
case DeeplinkApi.iosUniversalLinkSettings:
return _DeeplinkApiHandler.handleIosUniversalLinkSettings(
api,
queryParams,
deeplinkManager,
);
// ----- DTD api. -----
case DtdApi.apiGetDtdUri:
return _DtdApiHandler.handleGetDtdUri(api, dtd);
// ----- Unimplemented. -----
default:
return api.notImplemented();
}
}
static shelf.Response _encodeResponse(
Object? object, {
required ServerApi api,
}) {
return api.success(json.encode(object));
}
static Map<String, Object?> _wrapWithLogs(
Map<String, Object?> result,
List<String> logs,
) {
result[logsKey] = logs;
return result;
}
static shelf.Response? _checkRequiredParameters(
List<String> expectedParams, {
required Map<String, String> queryParams,
required ServerApi api,
required String requestName,
}) {
final missing = expectedParams.where(
(param) => !queryParams.containsKey(param),
);
return missing.isNotEmpty
? api.badRequest(
'[$requestName] missing required query parameters: '
'${missing.toList()}',
)
: null;
}
/// Accessing DevTools store file e.g., ~/.flutter-devtools/.devtools
static final _devToolsStore = DevToolsUsage();
/// Accessing Flutter store file e.g., ~/.flutter
static final _flutterStore = FlutterStore();
static DevToolsUsage get devToolsPreferences => _devToolsStore;
/// Provides read and write access to DevTools options files
/// (e.g. path/to/app/root/devtools_options.yaml).
static final _devToolsOptions = DevToolsOptions();
/// Logs a page view in the DevTools server.
///
/// In the open-source version of DevTools, Google Analytics handles this
/// without any need to involve the server.
shelf.Response logScreenView() => notImplemented();
/// A [shelf.Response] for API calls that succeeded.
///
/// The response optionally contains a single String [value].
shelf.Response success([String? value]) => shelf.Response.ok(value);
/// A [shelf.Response] for API calls that are forbidden for the current state
/// of the server.
shelf.Response forbidden([String? reason]) =>
shelf.Response.forbidden(reason);
/// A [shelf.Response] for API calls that encountered a request problem e.g.,
/// setActiveSurvey not called.
///
/// This is a 400 Bad Request response.
shelf.Response badRequest([String? error]) {
if (error != null) print(error);
return shelf.Response(HttpStatus.badRequest, body: error);
}
/// A [shelf.Response] for API calls that encountered a server error e.g.,
/// setActiveSurvey not called.
///
/// This is a 500 Internal Server Error response.
shelf.Response serverError([String? error, List<String>? logs]) {
if (error != null) print(error);
return shelf.Response(
HttpStatus.internalServerError,
body: error != null || logs != null
? jsonEncode(<String, Object?>{
if (error != null) errorKey: error,
if (logs != null) logsKey: logs,
})
: null,
);
}
/// A [shelf.Response] for API calls that have not been implemented in this
/// server.
///
/// This is a no-op 204 No Content response because returning 404 Not Found
/// creates unnecessary noise in the console.
shelf.Response notImplemented() => shelf.Response(HttpStatus.noContent);
}