Skip to content

Commit 8406614

Browse files
aranminoicmosuem
authored
Fix: Migrate off legacy JS/HTML APIs (#750)
* update: Migrate off legacy JS/HTML apis * update: use dart.library.js_interop in place of dart.library.html * update: dart format xhr_transport.dart and update dart sdk to v3.4.0 in workflows * update: use if instead of switch case in xhr_transport.dart * update: upgrade web package to v1.1.0 * refactor: use Uint8List for sending data over XHR rather than Int8List * refactor: eta-reduction of call to request.setRequestHeader * Update client_xhr_transport_test to avoid dart:html, updating xhr_transport to support testability * fixup tests --------- Co-authored-by: minoic <[email protected]> Co-authored-by: Moritz <[email protected]>
1 parent 7f9042f commit 8406614

File tree

5 files changed

+163
-52
lines changed

5 files changed

+163
-52
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
the connection, as defined in the gRPC spec.
77
* Upgrade to `package:lints` version 5.0.0 and Dart SDK version 3.5.0.
88
* Upgrade `example/grpc-web` code.
9+
* Update xhr transport to migrate off legacy JS/HTML apis.
910

1011
## 4.0.1
1112

lib/grpc_or_grpcweb.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// limitations under the License.
1515

1616
import 'src/client/grpc_or_grpcweb_channel_grpc.dart'
17-
if (dart.library.html) 'src/client/grpc_or_grpcweb_channel_web.dart';
17+
if (dart.library.js_interop) 'src/client/grpc_or_grpcweb_channel_web.dart';
1818
import 'src/client/http2_channel.dart';
1919
import 'src/client/options.dart';
2020

lib/src/client/transport/xhr_transport.dart

+132-22
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
// limitations under the License.
1515

1616
import 'dart:async';
17-
// ignore: deprecated_member_use (#756)
18-
import 'dart:html';
17+
import 'dart:js_interop';
1918
import 'dart:typed_data';
2019

2120
import 'package:meta/meta.dart';
21+
import 'package:web/web.dart';
2222

2323
import '../../client/call.dart';
2424
import '../../shared/message.dart';
@@ -31,7 +31,7 @@ import 'web_streams.dart';
3131
const _contentTypeKey = 'Content-Type';
3232

3333
class XhrTransportStream implements GrpcTransportStream {
34-
final HttpRequest _request;
34+
final IXMLHttpRequest _request;
3535
final ErrorHandler _onError;
3636
final Function(XhrTransportStream stream) _onDone;
3737
bool _headersReceived = false;
@@ -50,19 +50,20 @@ class XhrTransportStream implements GrpcTransportStream {
5050
{required ErrorHandler onError, required onDone})
5151
: _onError = onError,
5252
_onDone = onDone {
53-
_outgoingMessages.stream
54-
.map(frame)
55-
.listen((data) => _request.send(data), cancelOnError: true);
53+
_outgoingMessages.stream.map(frame).listen(
54+
(data) => _request.send(Uint8List.fromList(data).toJS),
55+
cancelOnError: true,
56+
onError: _onError);
5657

57-
_request.onReadyStateChange.listen((data) {
58+
_request.onReadyStateChange.listen((_) {
5859
if (_incomingProcessor.isClosed) {
5960
return;
6061
}
6162
switch (_request.readyState) {
62-
case HttpRequest.HEADERS_RECEIVED:
63+
case XMLHttpRequest.HEADERS_RECEIVED:
6364
_onHeadersReceived();
6465
break;
65-
case HttpRequest.DONE:
66+
case XMLHttpRequest.DONE:
6667
_onRequestDone();
6768
_close();
6869
break;
@@ -82,13 +83,11 @@ class XhrTransportStream implements GrpcTransportStream {
8283
if (_incomingProcessor.isClosed) {
8384
return;
8485
}
85-
// Use response over responseText as most browsers don't support
86-
// using responseText during an onProgress event.
87-
final responseString = _request.response as String;
86+
final responseText = _request.responseText;
8887
final bytes = Uint8List.fromList(
89-
responseString.substring(_requestBytesRead).codeUnits)
88+
responseText.substring(_requestBytesRead).codeUnits)
9089
.buffer;
91-
_requestBytesRead = responseString.length;
90+
_requestBytesRead = responseText.length;
9291
_incomingProcessor.add(bytes);
9392
});
9493

@@ -123,9 +122,11 @@ class XhrTransportStream implements GrpcTransportStream {
123122
if (!_headersReceived && !_validateResponseState()) {
124123
return;
125124
}
126-
if (_request.response == null) {
125+
if (_request.status != 200) {
127126
_onError(
128-
GrpcError.unavailable('XhrConnection request null response', null,
127+
GrpcError.unavailable(
128+
'Request failed with status: ${_request.status}',
129+
null,
129130
_request.responseText),
130131
StackTrace.current);
131132
return;
@@ -145,6 +146,110 @@ class XhrTransportStream implements GrpcTransportStream {
145146
}
146147
}
147148

149+
// XMLHttpRequest is an extension type and can't be extended or implemented.
150+
// This interface is used to allow for mocking XMLHttpRequest in tests of
151+
// XhrClientConnection.
152+
@visibleForTesting
153+
abstract interface class IXMLHttpRequest {
154+
Stream<Event> get onReadyStateChange;
155+
Stream<ProgressEvent> get onProgress;
156+
Stream<ProgressEvent> get onError;
157+
int get readyState;
158+
JSAny? get response;
159+
String get responseText;
160+
Map<String, String> get responseHeaders;
161+
int get status;
162+
163+
set responseType(String responseType);
164+
set withCredentials(bool withCredentials);
165+
166+
void abort();
167+
void open(
168+
String method,
169+
String url, [
170+
// external default is true
171+
bool async = true,
172+
String? username,
173+
String? password,
174+
]);
175+
void overrideMimeType(String mimeType);
176+
void send([JSAny? body]);
177+
void setRequestHeader(String header, String value);
178+
179+
// This method should only be used in production code.
180+
XMLHttpRequest toXMLHttpRequest();
181+
}
182+
183+
// IXMLHttpRequest that delegates to a real XMLHttpRequest.
184+
class XMLHttpRequestImpl implements IXMLHttpRequest {
185+
final XMLHttpRequest _xhr = XMLHttpRequest();
186+
187+
XMLHttpRequestImpl();
188+
189+
@override
190+
Stream<Event> get onReadyStateChange => _xhr.onReadyStateChange;
191+
@override
192+
Stream<ProgressEvent> get onProgress => _xhr.onProgress;
193+
@override
194+
Stream<ProgressEvent> get onError => _xhr.onError;
195+
@override
196+
int get readyState => _xhr.readyState;
197+
@override
198+
Map<String, String> get responseHeaders => _xhr.responseHeaders;
199+
@override
200+
JSAny? get response => _xhr.response;
201+
@override
202+
String get responseText => _xhr.responseText;
203+
@override
204+
int get status => _xhr.status;
205+
206+
@override
207+
set responseType(String responseType) {
208+
_xhr.responseType = responseType;
209+
}
210+
211+
@override
212+
set withCredentials(bool withCredentials) {
213+
_xhr.withCredentials = withCredentials;
214+
}
215+
216+
@override
217+
void abort() {
218+
_xhr.abort();
219+
}
220+
221+
@override
222+
void open(
223+
String method,
224+
String url, [
225+
bool async = true,
226+
String? username,
227+
String? password,
228+
]) {
229+
_xhr.open(method, url, async, username, password);
230+
}
231+
232+
@override
233+
void overrideMimeType(String mimeType) {
234+
_xhr.overrideMimeType(mimeType);
235+
}
236+
237+
@override
238+
void setRequestHeader(String header, String value) {
239+
_xhr.setRequestHeader(header, value);
240+
}
241+
242+
@override
243+
void send([JSAny? body]) {
244+
_xhr.send(body);
245+
}
246+
247+
@override
248+
XMLHttpRequest toXMLHttpRequest() {
249+
return _xhr;
250+
}
251+
}
252+
148253
class XhrClientConnection implements ClientConnection {
149254
final Uri uri;
150255

@@ -154,20 +259,20 @@ class XhrClientConnection implements ClientConnection {
154259

155260
@override
156261
String get authority => uri.authority;
262+
157263
@override
158264
String get scheme => uri.scheme;
159265

160-
void _initializeRequest(HttpRequest request, Map<String, String> metadata) {
161-
for (final header in metadata.keys) {
162-
request.setRequestHeader(header, metadata[header]!);
163-
}
266+
void _initializeRequest(
267+
IXMLHttpRequest request, Map<String, String> metadata) {
268+
metadata.forEach(request.setRequestHeader);
164269
// Overriding the mimetype allows us to stream and parse the data
165270
request.overrideMimeType('text/plain; charset=x-user-defined');
166271
request.responseType = 'text';
167272
}
168273

169274
@visibleForTesting
170-
HttpRequest createHttpRequest() => HttpRequest();
275+
IXMLHttpRequest createHttpRequest() => XMLHttpRequestImpl();
171276

172277
@override
173278
GrpcTransportStream makeRequest(String path, Duration? timeout,
@@ -195,11 +300,16 @@ class XhrClientConnection implements ClientConnection {
195300
_initializeRequest(request, metadata);
196301

197302
final transportStream =
198-
XhrTransportStream(request, onError: onError, onDone: _removeStream);
303+
_createXhrTransportStream(request, onError, _removeStream);
199304
_requests.add(transportStream);
200305
return transportStream;
201306
}
202307

308+
XhrTransportStream _createXhrTransportStream(IXMLHttpRequest request,
309+
ErrorHandler onError, void Function(XhrTransportStream stream) onDone) {
310+
return XhrTransportStream(request, onError: onError, onDone: onDone);
311+
}
312+
203313
void _removeStream(XhrTransportStream stream) {
204314
_requests.remove(stream);
205315
}

pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies:
2121
http2: ^2.2.0
2222
protobuf: '>=2.0.0 <4.0.0'
2323
clock: ^1.1.1
24+
web: ^1.1.0
2425

2526
dev_dependencies:
2627
build_runner: ^2.0.0

0 commit comments

Comments
 (0)