From 7f9042f79e2c67ebe54e2a8aa96fc238f6a948e1 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 29 Jan 2025 23:41:34 -0800 Subject: [PATCH 1/5] various analysis updates (#755) * various analysis updates * misc updates * remove unintentional dartdoc comment * Update CONTRIBUTING.md Co-authored-by: Moritz * add ignore comments for use of deprecated apis --------- Co-authored-by: Moritz --- CONTRIBUTING.md | 7 +++-- README.md | 5 ++-- analysis_options.yaml | 27 +++++++++---------- example/grpc-web/lib/app.dart | 1 + example/grpc-web/web/main.dart | 2 ++ lib/grpc.dart | 2 -- lib/src/client/call.dart | 6 ++--- lib/src/client/http2_connection.dart | 2 +- lib/src/client/transport/xhr_transport.dart | 1 + lib/src/server/handler.dart | 6 ++--- lib/src/shared/io_bits/io_bits_web.dart | 1 + pubspec.yaml | 17 ++++++------ .../client_xhr_transport_test.dart | 2 ++ ...server_handles_broken_connection_test.dart | 4 +-- test/timeline_test.dart | 2 +- 15 files changed, 43 insertions(+), 42 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 16fc729..c112fe5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ it should raise analysis issues as you edit; alternatively validate from the Terminal: ``` -dartanalyzer lib test +dart analyze ``` All analysis warnings and errors must be fixed; hints should be considered. @@ -33,8 +33,7 @@ All analysis warnings and errors must be fixed; hints should be considered. ## Running tests ``` -pub get -pub run test +dart test ``` gRPC-web tests require [`envoy`]( @@ -69,4 +68,4 @@ early on. so. ## Updating protobuf definitions -Sometimes we might need to update the generated dart files from the protos included in `lib/src/protos`. To do this, run the script `tool/regenerate.sh` from the project root and it will update the generated dart files in `lib/src/geneerated`. \ No newline at end of file +Sometimes we might need to update the generated dart files from the protos included in `lib/src/protos`. To do this, run the script `tool/regenerate.sh` from the project root and it will update the generated dart files in `lib/src/generated`. diff --git a/README.md b/README.md index f85542b..eb6017f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -The [Dart](https://www.dart.dev/) implementation of -[gRPC](https://grpc.io/): A high performance, open source, general RPC framework that puts mobile and HTTP/2 first. - [![Dart](https://github.com/grpc/grpc-dart/actions/workflows/dart.yml/badge.svg)](https://github.com/grpc/grpc-dart/actions/workflows/dart.yml) [![pub package](https://img.shields.io/pub/v/grpc.svg)](https://pub.dev/packages/grpc) +The [Dart](https://www.dart.dev/) implementation of +[gRPC](https://grpc.io/): A high performance, open source, general RPC framework that puts mobile and HTTP/2 first. ## Learn more diff --git a/analysis_options.yaml b/analysis_options.yaml index 5bcd150..18020d5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,23 +1,22 @@ -# https://dart.dev/guides/language/analysis-options include: package:lints/recommended.yaml analyzer: errors: - # These should be fixed or ignored in the proto generator + # These should be fixed or ignored in the proto generator. implementation_imports: ignore no_leading_underscores_for_local_identifiers: ignore + unintended_html_in_doc_comment: ignore linter: rules: - #true - always_declare_return_types: true - cancel_subscriptions: true - close_sinks: true - directives_ordering: true - omit_local_variable_types: true - prefer_final_locals: true - prefer_single_quotes: true - test_types_in_equals: true - prefer_relative_imports: true - #false - unintended_html_in_doc_comment: false + - always_declare_return_types + - cancel_subscriptions + - close_sinks + - directives_ordering + - omit_local_variable_types + - prefer_final_locals + - prefer_relative_imports + - prefer_single_quotes + # Enable once 3.7 is stable. + # - strict_top_level_inference + - test_types_in_equals diff --git a/example/grpc-web/lib/app.dart b/example/grpc-web/lib/app.dart index fce83c5..c28c60e 100644 --- a/example/grpc-web/lib/app.dart +++ b/example/grpc-web/lib/app.dart @@ -14,6 +14,7 @@ // limitations under the License. import 'dart:async'; +// ignore: deprecated_member_use (#756) import 'dart:html'; import 'src/generated/echo.pbgrpc.dart'; diff --git a/example/grpc-web/web/main.dart b/example/grpc-web/web/main.dart index 24da3b6..bd740ff 100644 --- a/example/grpc-web/web/main.dart +++ b/example/grpc-web/web/main.dart @@ -12,6 +12,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +// ignore: deprecated_member_use (#756) import 'dart:html'; import 'package:grpc/grpc_web.dart'; diff --git a/lib/grpc.dart b/lib/grpc.dart index 1c9593f..d2301a0 100644 --- a/lib/grpc.dart +++ b/lib/grpc.dart @@ -13,8 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ignore: dangling_library_doc_comments -/// Status detail types and error codes export 'package:grpc/src/generated/google/rpc/error_details.pb.dart'; export 'src/auth/auth.dart' show BaseAuthenticator; diff --git a/lib/src/client/call.dart b/lib/src/client/call.dart index 65f0de3..0cf773c 100644 --- a/lib/src/client/call.dart +++ b/lib/src/client/call.dart @@ -203,7 +203,7 @@ class ClientCall implements Response { } } - void onConnectionError(error) { + void onConnectionError(Object error) { _terminateWithError(GrpcError.unavailable('Error connecting: $error')); } @@ -398,7 +398,7 @@ class ClientCall implements Response { /// Handler for response errors. Forward the error to the [_responses] stream, /// wrapped if necessary. - void _onResponseError(error, StackTrace stackTrace) { + void _onResponseError(Object error, StackTrace stackTrace) { if (error is GrpcError) { _responseError(error, stackTrace); return; @@ -436,7 +436,7 @@ class ClientCall implements Response { /// Error handler for the requests stream. Something went wrong while trying /// to send the request to the server. Abort the request, and forward the /// error to the user code on the [_responses] stream. - void _onRequestError(error, StackTrace stackTrace) { + void _onRequestError(Object error, StackTrace stackTrace) { if (error is! GrpcError) { error = GrpcError.unknown(error.toString()); } diff --git a/lib/src/client/http2_connection.dart b/lib/src/client/http2_connection.dart index 4cce677..115f508 100644 --- a/lib/src/client/http2_connection.dart +++ b/lib/src/client/http2_connection.dart @@ -271,7 +271,7 @@ class Http2ClientConnection implements connection.ClientConnection { return _pendingCalls.isNotEmpty; } - void _handleConnectionFailure(error) { + void _handleConnectionFailure(Object error) { _disconnect(); if (_state == ConnectionState.shutdown || _state == ConnectionState.idle) { return; diff --git a/lib/src/client/transport/xhr_transport.dart b/lib/src/client/transport/xhr_transport.dart index 16b0dca..6798a77 100644 --- a/lib/src/client/transport/xhr_transport.dart +++ b/lib/src/client/transport/xhr_transport.dart @@ -14,6 +14,7 @@ // limitations under the License. import 'dart:async'; +// ignore: deprecated_member_use (#756) import 'dart:html'; import 'dart:typed_data'; diff --git a/lib/src/server/handler.dart b/lib/src/server/handler.dart index f28963c..e89557e 100644 --- a/lib/src/server/handler.dart +++ b/lib/src/server/handler.dart @@ -309,7 +309,7 @@ class ServerHandler extends ServiceCall { // -- Active state, outgoing response data -- - void _onResponse(response) { + void _onResponse(dynamic response) { try { final bytes = _descriptor.serialize(response); if (!_headersSent) { @@ -333,7 +333,7 @@ class ServerHandler extends ServiceCall { sendTrailers(); } - void _onResponseError(error, trace) { + void _onResponseError(Object error, StackTrace trace) { if (error is GrpcError) { _sendError(error, trace); } else { @@ -413,7 +413,7 @@ class ServerHandler extends ServiceCall { // -- All states, incoming error / stream closed -- - void _onError(error) { + void _onError(Object error) { // Exception from the incoming stream. Most likely a cancel request from the // client, so we treat it as such. _timeoutTimer?.cancel(); diff --git a/lib/src/shared/io_bits/io_bits_web.dart b/lib/src/shared/io_bits/io_bits_web.dart index e8dba5a..5290327 100644 --- a/lib/src/shared/io_bits/io_bits_web.dart +++ b/lib/src/shared/io_bits/io_bits_web.dart @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore: deprecated_member_use (#756) export 'dart:html' show HttpStatus; /// Unavailable on the web diff --git a/pubspec.yaml b/pubspec.yaml index 26f9ab1..b441f10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,13 @@ name: grpc -description: Dart implementation of gRPC, a high performance, open-source universal RPC framework. version: 4.0.2-wip - +description: Dart implementation of gRPC, a high performance, open-source universal RPC framework. repository: https://github.com/grpc/grpc-dart +topics: + - grpc + - protocols + - rpc + environment: sdk: ^3.5.0 @@ -31,10 +35,5 @@ dev_dependencies: fake_async: ^1.3.1 false_secrets: - - interop/server1.key - - test/data/localhost.key - -topics: - - grpc - - rpc - - protocols + - interop/server1.key + - test/data/localhost.key diff --git a/test/client_tests/client_xhr_transport_test.dart b/test/client_tests/client_xhr_transport_test.dart index 1c58077..3034445 100644 --- a/test/client_tests/client_xhr_transport_test.dart +++ b/test/client_tests/client_xhr_transport_test.dart @@ -12,10 +12,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + @TestOn('browser') library; import 'dart:async'; +// ignore: deprecated_member_use (#756) import 'dart:html'; import 'package:async/async.dart'; diff --git a/test/server_handles_broken_connection_test.dart b/test/server_handles_broken_connection_test.dart index c59c9da..689b525 100644 --- a/test/server_handles_broken_connection_test.dart +++ b/test/server_handles_broken_connection_test.dart @@ -77,7 +77,7 @@ class ClientData { {required this.address, required this.port, required this.sendPort}); } -void client(clientData) async { +void client(ClientData clientData) async { final channel = grpc.ClientChannel( clientData.address, port: clientData.port, @@ -107,7 +107,7 @@ Future main() async { ]); await server.serve(address: address, port: 0); final receivePort = ReceivePort(); - Isolate.spawn( + Isolate.spawn( client, ClientData( address: address, diff --git a/test/timeline_test.dart b/test/timeline_test.dart index 1866ea5..b0e936d 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -137,7 +137,7 @@ void checkFinishEvent(List events) { expect(e.length, 2); } -void main([args = const []]) { +void main(List args) { test('Test gRPC timeline logging', () async { final vmService = await testee(); final timeline = await vmService.getVMTimeline(); From 840661415df7d335cee98a28514de0bc02f7667e Mon Sep 17 00:00:00 2001 From: Aran Donohue Date: Tue, 18 Feb 2025 01:13:50 -0800 Subject: [PATCH 2/5] 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 Co-authored-by: Moritz --- CHANGELOG.md | 1 + lib/grpc_or_grpcweb.dart | 2 +- lib/src/client/transport/xhr_transport.dart | 154 +++++++++++++++--- pubspec.yaml | 1 + .../client_xhr_transport_test.dart | 57 ++++--- 5 files changed, 163 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 273cc7c..ad86f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ the connection, as defined in the gRPC spec. * Upgrade to `package:lints` version 5.0.0 and Dart SDK version 3.5.0. * Upgrade `example/grpc-web` code. +* Update xhr transport to migrate off legacy JS/HTML apis. ## 4.0.1 diff --git a/lib/grpc_or_grpcweb.dart b/lib/grpc_or_grpcweb.dart index b23bed5..049ceab 100644 --- a/lib/grpc_or_grpcweb.dart +++ b/lib/grpc_or_grpcweb.dart @@ -14,7 +14,7 @@ // limitations under the License. import 'src/client/grpc_or_grpcweb_channel_grpc.dart' - if (dart.library.html) 'src/client/grpc_or_grpcweb_channel_web.dart'; + if (dart.library.js_interop) 'src/client/grpc_or_grpcweb_channel_web.dart'; import 'src/client/http2_channel.dart'; import 'src/client/options.dart'; diff --git a/lib/src/client/transport/xhr_transport.dart b/lib/src/client/transport/xhr_transport.dart index 6798a77..693088c 100644 --- a/lib/src/client/transport/xhr_transport.dart +++ b/lib/src/client/transport/xhr_transport.dart @@ -14,11 +14,11 @@ // limitations under the License. import 'dart:async'; -// ignore: deprecated_member_use (#756) -import 'dart:html'; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:meta/meta.dart'; +import 'package:web/web.dart'; import '../../client/call.dart'; import '../../shared/message.dart'; @@ -31,7 +31,7 @@ import 'web_streams.dart'; const _contentTypeKey = 'Content-Type'; class XhrTransportStream implements GrpcTransportStream { - final HttpRequest _request; + final IXMLHttpRequest _request; final ErrorHandler _onError; final Function(XhrTransportStream stream) _onDone; bool _headersReceived = false; @@ -50,19 +50,20 @@ class XhrTransportStream implements GrpcTransportStream { {required ErrorHandler onError, required onDone}) : _onError = onError, _onDone = onDone { - _outgoingMessages.stream - .map(frame) - .listen((data) => _request.send(data), cancelOnError: true); + _outgoingMessages.stream.map(frame).listen( + (data) => _request.send(Uint8List.fromList(data).toJS), + cancelOnError: true, + onError: _onError); - _request.onReadyStateChange.listen((data) { + _request.onReadyStateChange.listen((_) { if (_incomingProcessor.isClosed) { return; } switch (_request.readyState) { - case HttpRequest.HEADERS_RECEIVED: + case XMLHttpRequest.HEADERS_RECEIVED: _onHeadersReceived(); break; - case HttpRequest.DONE: + case XMLHttpRequest.DONE: _onRequestDone(); _close(); break; @@ -82,13 +83,11 @@ class XhrTransportStream implements GrpcTransportStream { if (_incomingProcessor.isClosed) { return; } - // Use response over responseText as most browsers don't support - // using responseText during an onProgress event. - final responseString = _request.response as String; + final responseText = _request.responseText; final bytes = Uint8List.fromList( - responseString.substring(_requestBytesRead).codeUnits) + responseText.substring(_requestBytesRead).codeUnits) .buffer; - _requestBytesRead = responseString.length; + _requestBytesRead = responseText.length; _incomingProcessor.add(bytes); }); @@ -123,9 +122,11 @@ class XhrTransportStream implements GrpcTransportStream { if (!_headersReceived && !_validateResponseState()) { return; } - if (_request.response == null) { + if (_request.status != 200) { _onError( - GrpcError.unavailable('XhrConnection request null response', null, + GrpcError.unavailable( + 'Request failed with status: ${_request.status}', + null, _request.responseText), StackTrace.current); return; @@ -145,6 +146,110 @@ class XhrTransportStream implements GrpcTransportStream { } } +// XMLHttpRequest is an extension type and can't be extended or implemented. +// This interface is used to allow for mocking XMLHttpRequest in tests of +// XhrClientConnection. +@visibleForTesting +abstract interface class IXMLHttpRequest { + Stream get onReadyStateChange; + Stream get onProgress; + Stream get onError; + int get readyState; + JSAny? get response; + String get responseText; + Map get responseHeaders; + int get status; + + set responseType(String responseType); + set withCredentials(bool withCredentials); + + void abort(); + void open( + String method, + String url, [ + // external default is true + bool async = true, + String? username, + String? password, + ]); + void overrideMimeType(String mimeType); + void send([JSAny? body]); + void setRequestHeader(String header, String value); + + // This method should only be used in production code. + XMLHttpRequest toXMLHttpRequest(); +} + +// IXMLHttpRequest that delegates to a real XMLHttpRequest. +class XMLHttpRequestImpl implements IXMLHttpRequest { + final XMLHttpRequest _xhr = XMLHttpRequest(); + + XMLHttpRequestImpl(); + + @override + Stream get onReadyStateChange => _xhr.onReadyStateChange; + @override + Stream get onProgress => _xhr.onProgress; + @override + Stream get onError => _xhr.onError; + @override + int get readyState => _xhr.readyState; + @override + Map get responseHeaders => _xhr.responseHeaders; + @override + JSAny? get response => _xhr.response; + @override + String get responseText => _xhr.responseText; + @override + int get status => _xhr.status; + + @override + set responseType(String responseType) { + _xhr.responseType = responseType; + } + + @override + set withCredentials(bool withCredentials) { + _xhr.withCredentials = withCredentials; + } + + @override + void abort() { + _xhr.abort(); + } + + @override + void open( + String method, + String url, [ + bool async = true, + String? username, + String? password, + ]) { + _xhr.open(method, url, async, username, password); + } + + @override + void overrideMimeType(String mimeType) { + _xhr.overrideMimeType(mimeType); + } + + @override + void setRequestHeader(String header, String value) { + _xhr.setRequestHeader(header, value); + } + + @override + void send([JSAny? body]) { + _xhr.send(body); + } + + @override + XMLHttpRequest toXMLHttpRequest() { + return _xhr; + } +} + class XhrClientConnection implements ClientConnection { final Uri uri; @@ -154,20 +259,20 @@ class XhrClientConnection implements ClientConnection { @override String get authority => uri.authority; + @override String get scheme => uri.scheme; - void _initializeRequest(HttpRequest request, Map metadata) { - for (final header in metadata.keys) { - request.setRequestHeader(header, metadata[header]!); - } + void _initializeRequest( + IXMLHttpRequest request, Map metadata) { + metadata.forEach(request.setRequestHeader); // Overriding the mimetype allows us to stream and parse the data request.overrideMimeType('text/plain; charset=x-user-defined'); request.responseType = 'text'; } @visibleForTesting - HttpRequest createHttpRequest() => HttpRequest(); + IXMLHttpRequest createHttpRequest() => XMLHttpRequestImpl(); @override GrpcTransportStream makeRequest(String path, Duration? timeout, @@ -195,11 +300,16 @@ class XhrClientConnection implements ClientConnection { _initializeRequest(request, metadata); final transportStream = - XhrTransportStream(request, onError: onError, onDone: _removeStream); + _createXhrTransportStream(request, onError, _removeStream); _requests.add(transportStream); return transportStream; } + XhrTransportStream _createXhrTransportStream(IXMLHttpRequest request, + ErrorHandler onError, void Function(XhrTransportStream stream) onDone) { + return XhrTransportStream(request, onError: onError, onDone: onDone); + } + void _removeStream(XhrTransportStream stream) { _requests.remove(stream); } diff --git a/pubspec.yaml b/pubspec.yaml index b441f10..a89eb3f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: http2: ^2.2.0 protobuf: '>=2.0.0 <4.0.0' clock: ^1.1.1 + web: ^1.1.0 dev_dependencies: build_runner: ^2.0.0 diff --git a/test/client_tests/client_xhr_transport_test.dart b/test/client_tests/client_xhr_transport_test.dart index 3034445..84c805e 100644 --- a/test/client_tests/client_xhr_transport_test.dart +++ b/test/client_tests/client_xhr_transport_test.dart @@ -17,9 +17,8 @@ library; import 'dart:async'; -// ignore: deprecated_member_use (#756) -import 'dart:html'; - +import 'dart:js_interop'; +import 'dart:typed_data'; import 'package:async/async.dart'; import 'package:grpc/src/client/call.dart'; import 'package:grpc/src/client/transport/xhr_transport.dart'; @@ -28,12 +27,13 @@ import 'package:grpc/src/shared/status.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_transform/stream_transform.dart'; import 'package:test/test.dart'; +import 'package:web/web.dart'; final readyStateChangeEvent = - Event('readystatechange', canBubble: false, cancelable: false); + Event('readystatechange', EventInit(bubbles: false, cancelable: false)); final progressEvent = ProgressEvent('onloadstart'); -class MockHttpRequest extends Mock implements HttpRequest { +class MockHttpRequest extends Mock implements IXMLHttpRequest { MockHttpRequest({int? code}) : status = code ?? 200; // ignore: close_sinks StreamController readyStateChangeController = @@ -54,6 +54,10 @@ class MockHttpRequest extends Mock implements HttpRequest { @override final int status; + @override + String get responseText => + super.noSuchMethod(Invocation.getter(#responseText), returnValue: ''); + @override int get readyState => super.noSuchMethod(Invocation.getter(#readyState), returnValue: -1); @@ -73,7 +77,7 @@ class MockXhrClientConnection extends XhrClientConnection { final int _statusCode; @override - HttpRequest createHttpRequest() { + IXMLHttpRequest createHttpRequest() { final request = MockHttpRequest(code: _statusCode); latestRequest = request; return request; @@ -210,8 +214,8 @@ void main() { await stream.terminate(); final expectedData = frame(data); - expect(verify(connection.latestRequest.send(captureAny)).captured.single, - expectedData); + verify( + connection.latestRequest.send(Uint8List.fromList(expectedData).toJS)); }); test('Stream handles headers properly', () async { @@ -227,14 +231,13 @@ void main() { (error, _) => fail(error.toString())); when(transport.latestRequest.responseHeaders).thenReturn(responseHeaders); - when(transport.latestRequest.response) + when(transport.latestRequest.responseText) .thenReturn(String.fromCharCodes(frame([]))); // Set expectation for request readyState and generate two readyStateChange // events, so that incomingMessages stream completes. - final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE]; - when(transport.latestRequest.readyState) - .thenAnswer((_) => readyStates.removeAt(0)); + final readyStates = [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]; + when(transport.latestRequest.readyState).thenReturnInOrder(readyStates); transport.latestRequest.readyStateChangeController .add(readyStateChangeEvent); transport.latestRequest.readyStateChangeController @@ -269,13 +272,12 @@ void main() { final encodedString = String.fromCharCodes(encodedTrailers); when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders); - when(connection.latestRequest.response).thenReturn(encodedString); + when(connection.latestRequest.responseText).thenReturn(encodedString); // Set expectation for request readyState and generate events so that // incomingMessages stream completes. - final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE]; - when(connection.latestRequest.readyState) - .thenAnswer((_) => readyStates.removeAt(0)); + when(connection.latestRequest.readyState).thenReturnInOrder( + [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); connection.latestRequest.readyStateChangeController .add(readyStateChangeEvent); connection.latestRequest.progressController.add(progressEvent); @@ -305,13 +307,11 @@ void main() { final encodedString = String.fromCharCodes(encoded); when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders); - when(connection.latestRequest.response).thenReturn(encodedString); - + when(connection.latestRequest.responseText).thenReturn(encodedString); // Set expectation for request readyState and generate events so that // incomingMessages stream completes. - final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE]; - when(connection.latestRequest.readyState) - .thenAnswer((_) => readyStates.removeAt(0)); + when(connection.latestRequest.readyState).thenReturnInOrder( + [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); connection.latestRequest.readyStateChangeController .add(readyStateChangeEvent); connection.latestRequest.progressController.add(progressEvent); @@ -339,14 +339,13 @@ void main() { requestHeaders, (error, _) => fail(error.toString())); final data = List.filled(10, 224); when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders); - when(connection.latestRequest.response) + when(connection.latestRequest.responseText) .thenReturn(String.fromCharCodes(frame(data))); // Set expectation for request readyState and generate events, so that // incomingMessages stream completes. - final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE]; - when(connection.latestRequest.readyState) - .thenAnswer((_) => readyStates.removeAt(0)); + when(connection.latestRequest.readyState).thenReturnInOrder( + [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); connection.latestRequest.readyStateChangeController .add(readyStateChangeEvent); connection.latestRequest.progressController.add(progressEvent); @@ -371,7 +370,7 @@ void main() { const errorDetails = 'error details'; when(connection.latestRequest.responseHeaders) .thenReturn({'content-type': 'application/grpc+proto'}); - when(connection.latestRequest.readyState).thenReturn(HttpRequest.DONE); + when(connection.latestRequest.readyState).thenReturn(XMLHttpRequest.DONE); when(connection.latestRequest.responseText).thenReturn(errorDetails); connection.latestRequest.readyStateChangeController .add(readyStateChangeEvent); @@ -400,12 +399,12 @@ void main() { when(connection.latestRequest.responseHeaders).thenReturn(metadata); when(connection.latestRequest.readyState) - .thenReturn(HttpRequest.HEADERS_RECEIVED); + .thenReturn(XMLHttpRequest.HEADERS_RECEIVED); // At first invocation the response should be the the first message, after // that first + last messages. var first = true; - when(connection.latestRequest.response).thenAnswer((_) { + when(connection.latestRequest.responseText).thenAnswer((_) { if (first) { first = false; return encodedStrings[0]; @@ -413,7 +412,7 @@ void main() { return encodedStrings[0] + encodedStrings[1]; }); - final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE]; + final readyStates = [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]; when(connection.latestRequest.readyState) .thenAnswer((_) => readyStates.removeAt(0)); From 5ba28e3a1c2744415b0d696301eef5e59de534fb Mon Sep 17 00:00:00 2001 From: Aran Donohue Date: Fri, 21 Feb 2025 01:29:12 -0800 Subject: [PATCH 3/5] fix: Use package:web to get HttpStatus (#749) * Use package:web to get HttpStatus * docs: add CHANGELOG.md entry --------- Co-authored-by: Moritz --- CHANGELOG.md | 1 + lib/src/shared/io_bits/io_bits.dart | 2 +- lib/src/shared/io_bits/io_bits_web.dart | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad86f53..98a24a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Upgrade to `package:lints` version 5.0.0 and Dart SDK version 3.5.0. * Upgrade `example/grpc-web` code. * Update xhr transport to migrate off legacy JS/HTML apis. +* Use `package:web` to get `HttpStatus` ## 4.0.1 diff --git a/lib/src/shared/io_bits/io_bits.dart b/lib/src/shared/io_bits/io_bits.dart index c7409b5..5f0dd1f 100644 --- a/lib/src/shared/io_bits/io_bits.dart +++ b/lib/src/shared/io_bits/io_bits.dart @@ -13,4 +13,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -export 'io_bits_io.dart' if (dart.library.html) 'io_bits_web.dart'; +export 'io_bits_io.dart' if (dart.library.js_interop) 'io_bits_web.dart'; diff --git a/lib/src/shared/io_bits/io_bits_web.dart b/lib/src/shared/io_bits/io_bits_web.dart index 5290327..7f868d2 100644 --- a/lib/src/shared/io_bits/io_bits_web.dart +++ b/lib/src/shared/io_bits/io_bits_web.dart @@ -13,8 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ignore: deprecated_member_use (#756) -export 'dart:html' show HttpStatus; +export 'package:web/web.dart' show HttpStatus; /// Unavailable on the web class InternetAddress {} From 6dfb4b43f39649cb324d9f132fff9e65bc48ed2b Mon Sep 17 00:00:00 2001 From: Aran Donohue Date: Fri, 21 Feb 2025 05:11:03 -0800 Subject: [PATCH 4/5] fix: Updates the grpc-web example to avoid dart:html (#748) * 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 * Convert grpc-web example to package:web --------- Co-authored-by: minoic Co-authored-by: Moritz --- example/grpc-web/lib/app.dart | 28 ++++++++++++++++++---------- example/grpc-web/pubspec.yaml | 1 + example/grpc-web/web/main.dart | 11 ++++------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/example/grpc-web/lib/app.dart b/example/grpc-web/lib/app.dart index c28c60e..8c0d982 100644 --- a/example/grpc-web/lib/app.dart +++ b/example/grpc-web/lib/app.dart @@ -14,9 +14,7 @@ // limitations under the License. import 'dart:async'; -// ignore: deprecated_member_use (#756) -import 'dart:html'; - +import 'package:web/web.dart'; import 'src/generated/echo.pbgrpc.dart'; class EchoApp { @@ -57,13 +55,23 @@ class EchoApp { } void _addMessage(String message, String cssClass) { - final classes = cssClass.split(' '); - querySelector('#first')!.after(DivElement() - ..classes.add('row') - ..append(Element.tag('h2') - ..append(SpanElement() - ..classes.add('label') - ..classes.addAll(classes) + document.querySelector('#first')!.after(HTMLDivElement() + ..classList.add('row') + ..append(HTMLHeadingElement.h2() + ..append(HTMLSpanElement() + ..classList.add('label') + ..classList.addAll(cssClass) ..text = message))); } } + +// The documentation of DOMTokenList.add implies it can handle multiple classes, +// but in Chrome at least it does not. +extension AddAll on DOMTokenList { + void addAll(String cssClass) { + final classes = cssClass.split(' '); + for (final c in classes) { + add(c); + } + } +} diff --git a/example/grpc-web/pubspec.yaml b/example/grpc-web/pubspec.yaml index 10f80ce..4719ec1 100644 --- a/example/grpc-web/pubspec.yaml +++ b/example/grpc-web/pubspec.yaml @@ -9,6 +9,7 @@ dependencies: grpc: path: ../../ protobuf: ^3.0.0 + web: ^1.1.0 dev_dependencies: build_runner: ^2.4.13 diff --git a/example/grpc-web/web/main.dart b/example/grpc-web/web/main.dart index bd740ff..997a4bd 100644 --- a/example/grpc-web/web/main.dart +++ b/example/grpc-web/web/main.dart @@ -12,23 +12,20 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -// ignore: deprecated_member_use (#756) -import 'dart:html'; - import 'package:grpc/grpc_web.dart'; import 'package:grpc_web/app.dart'; import 'package:grpc_web/src/generated/echo.pbgrpc.dart'; +import 'package:web/web.dart'; void main() { final channel = GrpcWebClientChannel.xhr(Uri.parse('http://localhost:8080')); final service = EchoServiceClient(channel); final app = EchoApp(service); - final button = querySelector('#send') as ButtonElement; + final button = document.querySelector('#send') as HTMLButtonElement; button.onClick.listen((e) async { - final msg = querySelector('#msg') as TextInputElement; - final value = msg.value!.trim(); + final msg = document.querySelector('#msg') as HTMLInputElement; + final value = msg.value.trim(); msg.value = ''; if (value.isEmpty) return; From ebc838b66d5b02e8d46675bae06a75bbd153eb6c Mon Sep 17 00:00:00 2001 From: Aran Donohue Date: Fri, 21 Feb 2025 05:18:14 -0800 Subject: [PATCH 5/5] fix: update grpc_web_server.dart envoy config to support newer envoy version (#760) Co-authored-by: Moritz --- test/grpc_web_server.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/grpc_web_server.dart b/test/grpc_web_server.dart index 57d80b6..a1098eb 100644 --- a/test/grpc_web_server.dart +++ b/test/grpc_web_server.dart @@ -81,8 +81,14 @@ static_resources: expose_headers: custom-header-1,grpc-status,grpc-message http_filters: - name: envoy.filters.http.grpc_web + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb - name: envoy.filters.http.cors + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: echo_service connect_timeout: 0.25s