14
14
// limitations under the License.
15
15
16
16
import 'dart:async' ;
17
- // ignore: deprecated_member_use (#756)
18
- import 'dart:html' ;
17
+ import 'dart:js_interop' ;
19
18
import 'dart:typed_data' ;
20
19
21
20
import 'package:meta/meta.dart' ;
21
+ import 'package:web/web.dart' ;
22
22
23
23
import '../../client/call.dart' ;
24
24
import '../../shared/message.dart' ;
@@ -31,7 +31,7 @@ import 'web_streams.dart';
31
31
const _contentTypeKey = 'Content-Type' ;
32
32
33
33
class XhrTransportStream implements GrpcTransportStream {
34
- final HttpRequest _request;
34
+ final IXMLHttpRequest _request;
35
35
final ErrorHandler _onError;
36
36
final Function (XhrTransportStream stream) _onDone;
37
37
bool _headersReceived = false ;
@@ -50,19 +50,20 @@ class XhrTransportStream implements GrpcTransportStream {
50
50
{required ErrorHandler onError, required onDone})
51
51
: _onError = onError,
52
52
_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);
56
57
57
- _request.onReadyStateChange.listen ((data ) {
58
+ _request.onReadyStateChange.listen ((_ ) {
58
59
if (_incomingProcessor.isClosed) {
59
60
return ;
60
61
}
61
62
switch (_request.readyState) {
62
- case HttpRequest .HEADERS_RECEIVED :
63
+ case XMLHttpRequest .HEADERS_RECEIVED :
63
64
_onHeadersReceived ();
64
65
break ;
65
- case HttpRequest .DONE :
66
+ case XMLHttpRequest .DONE :
66
67
_onRequestDone ();
67
68
_close ();
68
69
break ;
@@ -82,13 +83,11 @@ class XhrTransportStream implements GrpcTransportStream {
82
83
if (_incomingProcessor.isClosed) {
83
84
return ;
84
85
}
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;
88
87
final bytes = Uint8List .fromList (
89
- responseString .substring (_requestBytesRead).codeUnits)
88
+ responseText .substring (_requestBytesRead).codeUnits)
90
89
.buffer;
91
- _requestBytesRead = responseString .length;
90
+ _requestBytesRead = responseText .length;
92
91
_incomingProcessor.add (bytes);
93
92
});
94
93
@@ -123,9 +122,11 @@ class XhrTransportStream implements GrpcTransportStream {
123
122
if (! _headersReceived && ! _validateResponseState ()) {
124
123
return ;
125
124
}
126
- if (_request.response == null ) {
125
+ if (_request.status != 200 ) {
127
126
_onError (
128
- GrpcError .unavailable ('XhrConnection request null response' , null ,
127
+ GrpcError .unavailable (
128
+ 'Request failed with status: ${_request .status }' ,
129
+ null ,
129
130
_request.responseText),
130
131
StackTrace .current);
131
132
return ;
@@ -145,6 +146,110 @@ class XhrTransportStream implements GrpcTransportStream {
145
146
}
146
147
}
147
148
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
+
148
253
class XhrClientConnection implements ClientConnection {
149
254
final Uri uri;
150
255
@@ -154,20 +259,20 @@ class XhrClientConnection implements ClientConnection {
154
259
155
260
@override
156
261
String get authority => uri.authority;
262
+
157
263
@override
158
264
String get scheme => uri.scheme;
159
265
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);
164
269
// Overriding the mimetype allows us to stream and parse the data
165
270
request.overrideMimeType ('text/plain; charset=x-user-defined' );
166
271
request.responseType = 'text' ;
167
272
}
168
273
169
274
@visibleForTesting
170
- HttpRequest createHttpRequest () => HttpRequest ();
275
+ IXMLHttpRequest createHttpRequest () => XMLHttpRequestImpl ();
171
276
172
277
@override
173
278
GrpcTransportStream makeRequest (String path, Duration ? timeout,
@@ -195,11 +300,16 @@ class XhrClientConnection implements ClientConnection {
195
300
_initializeRequest (request, metadata);
196
301
197
302
final transportStream =
198
- XhrTransportStream (request, onError: onError, onDone : _removeStream);
303
+ _createXhrTransportStream (request, onError, _removeStream);
199
304
_requests.add (transportStream);
200
305
return transportStream;
201
306
}
202
307
308
+ XhrTransportStream _createXhrTransportStream (IXMLHttpRequest request,
309
+ ErrorHandler onError, void Function (XhrTransportStream stream) onDone) {
310
+ return XhrTransportStream (request, onError: onError, onDone: onDone);
311
+ }
312
+
203
313
void _removeStream (XhrTransportStream stream) {
204
314
_requests.remove (stream);
205
315
}
0 commit comments