Open minoic opened 1 month ago
Thanks for reporting this, we should migrate package:grpc
off dart:html
. cc @kevmoo
I received a suggestion for an alternative solution using GPT-4 through gpt4o. This approach has been working well for me, but it still needs more testing. I hope everyone can review and provide feedback.
original: https://github.com/grpc/grpc-dart/blob/master/lib/src/client/transport/xhr_transport.dart
Please find the suggested solution below:
// Copyright (c) 2018, the gRPC project authors. Please see the AUTHORS file
// for details. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// 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.
import 'dart:async'; import 'dart:typed_data'; import 'package:web/web.dart'; import 'package:meta/meta.dart'; import '../../client/call.dart'; import '../../shared/message.dart'; import '../../shared/status.dart'; import '../connection.dart'; import 'cors.dart' as cors; import 'transport.dart'; import 'web_streams.dart'; import 'dart:js_interop';
@JS('Uint8Array') @staticInterop class JSUint8Array { external factory JSUint8Array(JSAny data); }
const _contentTypeKey = 'Content-Type';
class XhrTransportStream implements GrpcTransportStream {
final XMLHttpRequest _request;
final ErrorHandler _onError;
final Function(XhrTransportStream stream) _onDone;
bool _headersReceived = false;
int _requestBytesRead = 0;
final StreamController
@override
Stream
@override
StreamSink<List
XhrTransportStream(this._request, {required ErrorHandler onError, required onDone}) : _onError = onError, _onDone = onDone { _outgoingMessages.stream.map(frame).listen((data) { _sendRequest(data); }, cancelOnError: true);
_request.onReadyStateChange.listen((_) {
if (_incomingProcessor.isClosed) {
return;
}
switch (_request.readyState) {
case 2:
_onHeadersReceived();
break;
case 4:
_onRequestDone();
_close();
break;
}
});
_request.onError.listen((ProgressEvent event) {
if (_incomingProcessor.isClosed) {
return;
}
_onError(GrpcError.unavailable('XhrConnection connection-error'),
StackTrace.current);
terminate();
});
_request.onProgress.listen((_) {
if (_incomingProcessor.isClosed) {
return;
}
final responseText = _request.responseText;
final bytes = Uint8List.fromList(
responseText.substring(_requestBytesRead).codeUnits)
.buffer;
_requestBytesRead = responseText.length;
_incomingProcessor.add(bytes);
});
_incomingProcessor.stream
.transform(GrpcWebDecoder())
.transform(grpcDecompressor())
.listen(_incomingMessages.add,
onError: _onError, onDone: _incomingMessages.close);
}
void _sendRequest(List
void _onHeadersReceived() { _headersReceived = true; final responseHeaders = _request.getAllResponseHeaders(); final headersMap = parseHeaders(responseHeaders); final metadata = GrpcMetadata(headersMap); _incomingMessages.add(metadata); }
void _onRequestDone() { if (!_headersReceived) { _onHeadersReceived(); } if (_request.status != 200) { _onError( GrpcError.unavailable( 'Request failed with status: ${_request.status}', null, _request.responseText), StackTrace.current); } }
bool _validateResponseState() { try { final headersMap = parseHeaders(_request.getAllResponseHeaders()); validateHttpStatusAndContentType(_request.status, headersMap, rawResponse: _request.responseText); return true; } catch (e, st) { _onError(e, st); return false; } }
void _close() { _incomingProcessor.close(); _outgoingMessages.close(); _onDone(this); }
@override
Future
class XhrClientConnection implements ClientConnection {
final Uri uri;
final _requests =
XhrClientConnection(this.uri);
@override String get authority => uri.authority; @override String get scheme => uri.scheme;
void _initializeRequest( XMLHttpRequest request, Map<String, String> metadata) { metadata.forEach((key, value) { request.setRequestHeader(key, value); }); request.overrideMimeType('text/plain; charset=x-user-defined'); request.responseType = 'text'; }
@visibleForTesting XMLHttpRequest createHttpRequest() => XMLHttpRequest();
@override GrpcTransportStream makeRequest(String path, Duration? timeout, Map<String, String> metadata, ErrorHandler onError, {CallOptions? callOptions}) { if (_getContentTypeHeader(metadata) == null) { metadata['Content-Type'] = 'application/grpc-web+proto'; metadata['X-User-Agent'] = 'grpc-web-dart/0.1'; metadata['X-Grpc-Web'] = '1'; }
var requestUri = uri.resolve(path);
if (callOptions is WebCallOptions &&
callOptions.bypassCorsPreflight == true) {
requestUri = cors.moveHttpHeadersToQueryParam(metadata, requestUri);
}
final request = createHttpRequest();
request.open('POST', requestUri.toString());
if (callOptions is WebCallOptions && callOptions.withCredentials == true) {
request.withCredentials = true;
}
_initializeRequest(request, metadata);
final transportStream =
XhrTransportStream(request, onError: onError, onDone: _removeStream);
_requests.add(transportStream);
return transportStream;
}
void _removeStream(XhrTransportStream stream) { _requests.remove(stream); }
@override
Future
@override void dispatchCall(ClientCall call) { call.onConnectionReady(this); }
@override
Future
@override set onStateChanged(void Function(ConnectionState) cb) { // Do nothing. } }
MapEntry<String, String>? _getContentTypeHeader(Map<String, String> metadata) { for (var entry in metadata.entries) { if (entry.key.toLowerCase() == _contentTypeKey.toLowerCase()) { return entry; } } return null; }
Map<String, String> parseHeaders(String rawHeaders) { final headers = <String, String>{}; final lines = rawHeaders.split('\r\n'); for (var line in lines) { final index = line.indexOf(': '); if (index != -1) { final key = line.substring(0, index); final value = line.substring(index + 2); headers[key] = value; } } return headers; }
Is there any ETA on this @mosuem , @kevmoo ?
Everyone is busy. Happy to accept a pull request!
+1 Error: /home/flutter/.pub-cache/hosted/pub.dev/grpc-3.2.4/lib/src/client/transport/xhr_transport.dart:17:8: Error: Dart library 'dart:html' is not available on this platform.
+1 Error: /home/flutter/.pub-cache/hosted/pub.dev/grpc-3.2.4/lib/src/client/transport/xhr_transport.dart:17:8: Error: Dart library 'dart:html' is not available on this platform.
You can try my fixed version by editing pubspec.yml
file:
dependencies:
grpc:
git:
url: https://github.com/minoic/grpc-dart.git
It seems to work but I can't guarantee its stability.
Wasm support is now stable since flutter 3.22 published, i got UNAVAILABLE error while testing my web app with wasm.
Version of the grpc-dart packages used: v3.2.4 and grpc/grpc-dart master branch.
Repro steps
GrpcOrGrpcWebClientChannel.toSeparateEndpoints
, write some request code.flutter build web --wasm
.Expected result: The request successfully sent as canvaskit mode does.
Actual result:
Details
This error could be caused by two points:
grpc_or_grpcweb.dart
. Because of wasm has no html package, conditional import recognized wasm as non-web platform.xhr_transport.dart
will be used to transport data, which uses html package, but "dart:html is being replaced with package:web. Package maintainers should migrate to package:web as soon as possible to be compatible with Wasm. Read the Migrate to package:web page for guidance."I tried to fix it in https://github.com/minoic/grpc-dart, it worked in my case but more test should be performed later.