// Copyright (c) 2015, 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. part of "dart:developer"; /// A response to a service protocol extension RPC. /// /// If the RPC was successful, use [ServiceExtensionResponse.result], otherwise /// use [ServiceExtensionResponse.error]. final class ServiceExtensionResponse { /// The result of a successful service protocol extension RPC. final String? result; /// The error code associated with a failed service protocol extension RPC. final int? errorCode; /// The details of a failed service protocol extension RPC. final String? errorDetail; /// Creates a successful response to a service protocol extension RPC. /// /// Requires [result] to be a JSON object encoded as a string. When forming /// the JSON-RPC message [result] will be inlined directly. ServiceExtensionResponse.result(String result) : result = result, errorCode = null, errorDetail = null; /// Creates an error response to a service protocol extension RPC. /// /// Requires [errorCode] to be [invalidParams] or between [extensionErrorMin] /// and [extensionErrorMax]. Requires [errorDetail] to be a JSON object /// encoded as a string. When forming the JSON-RPC message [errorDetail] will /// be inlined directly. ServiceExtensionResponse.error(int errorCode, String errorDetail) : result = null, errorCode = errorCode, errorDetail = errorDetail { _validateErrorCode(errorCode); } /// Invalid method parameter(s) error code. static const invalidParams = -32602; /// Generic extension error code. static const extensionError = -32000; /// Maximum extension provided error code. static const extensionErrorMax = -32000; /// Minimum extension provided error code. static const extensionErrorMin = -32016; static String _errorCodeMessage(int errorCode) { _validateErrorCode(errorCode); if (errorCode == invalidParams) { return "Invalid params"; } return "Server error"; } static _validateErrorCode(int errorCode) { if (errorCode == invalidParams) return; if ((errorCode >= extensionErrorMin) && (errorCode <= extensionErrorMax)) { return; } throw new ArgumentError.value(errorCode, "errorCode", "Out of range"); } /// Determines if this response represents an error. bool isError() => (errorCode != null) && (errorDetail != null); // ignore: unused_element, called from runtime/lib/developer.dart String _toString() { return result ?? json.encode({ 'code': errorCode!, 'message': _errorCodeMessage(errorCode!), 'data': {'details': errorDetail!}, }); } } /// A service protocol extension handler. Registered with [registerExtension]. /// /// Must complete to a [ServiceExtensionResponse]. [method] is the method name /// of the service protocol request, and [parameters] is a map holding the /// parameters to the service protocol request. /// /// *NOTE*: all parameter names and values are encoded as strings. typedef Future ServiceExtensionHandler( String method, Map parameters, ); /// Register a [ServiceExtensionHandler] that will be invoked in this isolate /// for [method]. *NOTE*: Service protocol extensions must be registered /// in each isolate. /// /// *NOTE*: [method] must begin with 'ext.' and you should use the following /// structure to avoid conflicts with other packages: 'ext.package.command'. /// That is, immediately following the 'ext.' prefix, should be the registering /// package name followed by another period ('.') and then the command name. /// For example: 'ext.dart.io.getOpenFiles'. /// /// Because service extensions are isolate specific, clients using extensions /// must always include an 'isolateId' parameter with each RPC. void registerExtension(String method, ServiceExtensionHandler handler) { if (!method.startsWith('ext.')) { throw new ArgumentError.value(method, 'method', 'Must begin with ext.'); } if (_lookupExtension(method) != null) { throw new ArgumentError('Extension already registered: $method'); } final zoneHandler = Zone.current.bindBinaryCallback(handler); _registerExtension(method, zoneHandler); } /// Whether the "Extension" stream currently has at least one listener. /// /// A client of the VM service can register as a listener /// on the extension stream using `listenStream` method. /// The extension stream has a listener while at least one such /// client has registered as a listener, and has not yet disconnected /// again. /// /// Calling [postEvent] while the stream has listeners will attempt to /// deliver that event to all current listeners, /// although a listener can disconnect before the event is delivered. /// Calling [postEvent] when the stream has no listener means that /// no-one will receive the event, and the call is effectively a no-op. @pragma("vm:recognized", "other") @pragma("vm:prefer-inline") @pragma("vm:idempotent") @Since('2.18') external bool get extensionStreamHasListener; /// Post an event of [eventKind] with payload of [eventData] to the "Extension" /// event stream. /// /// If [extensionStreamHasListener] is false, this method is a no-op. /// Override [stream] to set the destination stream that the event should be /// posted to. The [stream] may not start with an underscore or be a core VM /// Service stream. void postEvent( String eventKind, Map eventData, { @Since('3.0 ') String stream = 'Extension', }) { const destinationStreamKey = '__destinationStream'; // Keep protected streams in sync with `streams_` in runtime/vm/service.cc // `Extension` is the only stream that should not be protected here. final protectedStreams = [ 'VM', 'Isolate', 'Debug', 'GC', '_Echo', 'HeapSnapshot', 'Logging', 'Timer', 'Timeline', 'Profiler', ]; if (protectedStreams.contains(stream)) { throw ArgumentError.value( stream, 'stream', 'Cannot be a protected stream.', ); } else if (stream.startsWith('_')) { throw ArgumentError.value( stream, 'stream', 'Cannot start with an underscore.', ); } if (!extensionStreamHasListener) { return; } Map mutableEventData = Map.from(eventData); // Shallow copy. mutableEventData[destinationStreamKey] = stream; String eventDataAsString = json.encode(mutableEventData); _postEvent(eventKind, eventDataAsString); } external void _postEvent(String eventKind, String eventData); // Both of these functions are written inside C++ to avoid updating the data // structures in Dart, getting an OOB, and observing stale state. Do not move // these into Dart code unless you can ensure that the operations will can be // done atomically. Native code lives in vm/isolate.cc- // LookupServiceExtensionHandler and RegisterServiceExtensionHandler. external ServiceExtensionHandler? _lookupExtension(String method); external _registerExtension(String method, ServiceExtensionHandler handler);