-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
extension.dart
201 lines (179 loc) · 7.69 KB
/
extension.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// 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 {
// TODO: When NNBD is complete, delete the following line.
checkNotNullable(result, "result");
}
/// 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);
// TODO: When NNBD is complete, delete the following line.
checkNotNullable(errorDetail, "errorDetail");
}
/// 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) {
// TODO: When NNBD is complete, delete the following line.
checkNotNullable(errorCode, "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<ServiceExtensionResponse> ServiceExtensionHandler(
String method, Map<String, String> 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) {
// TODO: When NNBD is complete, delete the following line.
checkNotNullable(method, 'method');
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');
}
// TODO: When NNBD is complete, delete the following line.
checkNotNullable(handler, 'handler');
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 = <String>[
'VM',
'Isolate',
'Debug',
'GC',
'_Echo',
'HeapSnapshot',
'Logging',
'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;
}
// TODO: When NNBD is complete, delete the following two lines.
checkNotNullable(eventKind, 'eventKind');
checkNotNullable(eventData, 'eventData');
checkNotNullable(stream, 'stream');
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);