@@ -3,20 +3,17 @@ import 'dart:collection';
33import 'dart:convert' ;
44import 'dart:typed_data' ;
55
6+ import 'package:gql_exec/gql_exec.dart' ;
7+ import 'package:graphql/src/core/query_options.dart' show WithType;
68import 'package:graphql/src/links/gql_links.dart' ;
79import 'package:graphql/src/utilities/platform.dart' ;
810import 'package:meta/meta.dart' ;
9-
10- import 'package:graphql/src/core/query_options.dart' show WithType;
11- import 'package:gql_exec/gql_exec.dart' ;
12-
13- import 'package:stream_channel/stream_channel.dart' ;
14- import 'package:web_socket_channel/web_socket_channel.dart' ;
15- import 'package:web_socket_channel/status.dart' as ws_status;
16-
1711import 'package:rxdart/rxdart.dart' ;
12+ import 'package:stream_channel/stream_channel.dart' ;
1813import 'package:uuid/uuid.dart' ;
1914import 'package:uuid/uuid_util.dart' ;
15+ import 'package:web_socket_channel/status.dart' as ws_status;
16+ import 'package:web_socket_channel/web_socket_channel.dart' ;
2017
2118import './websocket_messages.dart' ;
2219
@@ -144,6 +141,13 @@ class SocketClientConfig {
144141 }
145142}
146143
144+ class SocketSubProtocol {
145+ SocketSubProtocol ._();
146+
147+ static const String graphqlWs = "graphql-ws" ;
148+ static const String graphqlTransportWs = "graphql-transport-ws" ;
149+ }
150+
147151/// Wraps a standard web socket instance to marshal and un-marshal the server /
148152/// client payloads into dart object representation.
149153///
@@ -155,7 +159,7 @@ class SocketClientConfig {
155159class SocketClient {
156160 SocketClient (
157161 this .url, {
158- this .protocols = const [ 'graphql-ws' ] ,
162+ this .protocol = SocketSubProtocol .graphqlWs ,
159163 this .config = const SocketClientConfig (),
160164 @visibleForTesting this .randomBytesForUuid,
161165 @visibleForTesting this .onMessage,
@@ -166,7 +170,7 @@ class SocketClient {
166170
167171 Uint8List ? randomBytesForUuid;
168172 final String url;
169- final Iterable < String > ? protocols ;
173+ final String protocol ;
170174 final SocketClientConfig config;
171175
172176 final BehaviorSubject <SocketConnectionState > _connectionStateController =
@@ -179,6 +183,7 @@ class SocketClient {
179183 bool _wasDisposed = false ;
180184
181185 Timer ? _reconnectTimer;
186+ Timer ? _pingTimer;
182187
183188 @visibleForTesting
184189 GraphQLWebSocketChannel ? socketChannel;
@@ -239,17 +244,34 @@ class SocketClient {
239244 // Even though config.connect is sync, we call async in order to make the
240245 // SocketConnectionState.connected attribution not overload SocketConnectionState.connecting
241246 var connection =
242- await config.connect (uri: Uri .parse (url), protocols: protocols );
247+ await config.connect (uri: Uri .parse (url), protocols: [protocol] );
243248 socketChannel = connection.forGraphQL ();
244249 _connectionStateController.add (SocketConnectionState .connected);
245250 _write (initOperation);
246251
247252 if (config.inactivityTimeout != null ) {
248- _disconnectOnKeepAliveTimeout (_messages);
253+ if (protocol == SocketSubProtocol .graphqlWs) {
254+ _disconnectOnKeepAliveTimeout (_messages);
255+ }
256+ if (protocol == SocketSubProtocol .graphqlTransportWs) {
257+ _enqueuePing ();
258+ }
249259 }
250260
251261 _messageSubscription = _messages.listen (
252- onMessage,
262+ (message) {
263+ if (onMessage != null ) {
264+ onMessage !(message);
265+ }
266+
267+ if (protocol == SocketSubProtocol .graphqlTransportWs) {
268+ if (message.type == 'ping' ) {
269+ _write (PongMessage ());
270+ } else if (message.type == 'pong' ) {
271+ _enqueuePing ();
272+ }
273+ }
274+ },
253275 onDone: onConnectionLost,
254276 // onDone will not be triggered if the subscription is
255277 // auto-cancelled on error; make sure to pass false
@@ -276,6 +298,7 @@ class SocketClient {
276298 }
277299 print ('Disconnected from websocket.' );
278300 _reconnectTimer? .cancel ();
301+ _pingTimer? .cancel ();
279302 _keepAliveSubscription? .cancel ();
280303 _messageSubscription? .cancel ();
281304
@@ -302,6 +325,14 @@ class SocketClient {
302325 }
303326 }
304327
328+ void _enqueuePing () {
329+ _pingTimer? .cancel ();
330+ _pingTimer = new Timer (
331+ config.inactivityTimeout! ,
332+ () => _write (PingMessage ()),
333+ );
334+ }
335+
305336 /// Closes the underlying socket if connected, and stops reconnection attempts.
306337 /// After calling this method, this [SocketClient] instance must be considered
307338 /// unusable. Instead, create a new instance of this class.
@@ -314,6 +345,7 @@ class SocketClient {
314345 _wasDisposed = true ;
315346 print ('Disposing socket client..' );
316347 _reconnectTimer? .cancel ();
348+ _pingTimer? .cancel ();
317349 _keepAliveSubscription? .cancel ();
318350
319351 await Future .wait ([
@@ -385,6 +417,10 @@ class SocketClient {
385417 return message.id == id;
386418 }
387419
420+ if (message is SubscriptionNext ) {
421+ return message.id == id;
422+ }
423+
388424 if (message is SubscriptionError ) {
389425 return message.id == id;
390426 }
@@ -422,18 +458,34 @@ class SocketClient {
422458 parse (message.toJson ()),
423459 ));
424460
461+ dataErrorComplete
462+ .where ((message) => message is SubscriptionNext )
463+ .cast <SubscriptionNext >()
464+ .listen ((message) => response.add (
465+ parse (message.toJson ()),
466+ ));
467+
425468 dataErrorComplete
426469 .where ((message) => message is SubscriptionError )
427470 .cast <SubscriptionError >()
428471 .listen ((message) => response.addError (message));
429472
430473 if (! _subscriptionInitializers[id]! .hasBeenTriggered) {
431- _write (
432- StartOperation (
433- id,
434- serialize (payload),
435- ),
436- );
474+ if (protocol == SocketSubProtocol .graphqlTransportWs) {
475+ _write (
476+ SubscribeOperation (
477+ id,
478+ serialize (payload),
479+ ),
480+ );
481+ } else {
482+ _write (
483+ StartOperation (
484+ id,
485+ serialize (payload),
486+ ),
487+ );
488+ }
437489 _subscriptionInitializers[id]! .hasBeenTriggered = true ;
438490 }
439491 });
@@ -445,7 +497,8 @@ class SocketClient {
445497 _subscriptionInitializers.remove (id);
446498
447499 sub? .cancel ();
448- if (_connectionStateController.value == SocketConnectionState .connected &&
500+ if (protocol == SocketSubProtocol .graphqlWs &&
501+ _connectionStateController.value == SocketConnectionState .connected &&
449502 socketChannel != null ) {
450503 _write (StopOperation (id));
451504 }
0 commit comments