test(client): integration test websocket
This test can be run both on `vm` and `chrome`, as now we are using a package to have universal API for websocket.
MODIFY
packages/graphql/lib/src/link/web_socket/link_web_socket.dart
packages/graphql/lib/src/link/web_socket/link_web_socket.dart
@@ -2,7 +2,6 @@ import 'package:graphql/src/link/fetch_result.dart';
import 'package:graphql/src/link/link.dart';
import 'package:graphql/src/link/operation.dart';
import 'package:graphql/src/socket_client.dart';
-import 'package:graphql/src/utilities/helpers.dart';
import 'package:graphql/src/websocket/messages.dart';
import 'package:meta/meta.dart';
@@ -23,7 +22,6 @@ class WebSocketLink extends Link {
/// Creates a new [WebSocketLink] instance with the specified config.
WebSocketLink(
{@required this.url,
- this.headers,
this.reconnectOnHeaderChange = true,
this.config = const SocketClientConfig()})
: super() {
@@ -31,7 +29,6 @@ class WebSocketLink extends Link {
}
final String url;
- final Map<String, dynamic> headers;
final bool reconnectOnHeaderChange;
final SocketClientConfig config;
@@ -44,17 +41,8 @@ class WebSocketLink extends Link {
if (context != null && context.containsKey('headers')) {
concatHeaders.addAll(context['headers'] as Map<String, dynamic>);
}
- if (headers != null) {
- concatHeaders.addAll(headers);
- }
- if (_socketClient == null ||
- (reconnectOnHeaderChange &&
- areDifferentVariables(_socketClient.headers, concatHeaders))) {
- if (_socketClient != null) {
- print(
- 'Creating a new socket client, because the headers have changed..');
- }
+ if (_socketClient == null) {
connectOrReconnect(headers: concatHeaders);
}
@@ -70,7 +58,7 @@ class WebSocketLink extends Link {
void connectOrReconnect({Map<String, dynamic> headers}) {
_socketClient?.dispose();
_socketClient =
- SocketClient(url, headers: headers ?? this.headers, config: config);
+ SocketClient(url, config: config);
}
/// Disposes the underlying socket client explicitly. Only use this, if you want to disconnect from

MODIFY
packages/graphql/lib/src/socket_client.dart
packages/graphql/lib/src/socket_client.dart
import 'dart:async';
import 'dart:convert';
+import 'dart:typed_data';
+import 'package:meta/meta.dart';
import 'package:websocket/websocket.dart' show WebSocket, WebSocketStatus;
import 'package:rxdart/rxdart.dart';
-import 'package:uuid/uuid.dart';
+import 'package:uuid_enhanced/uuid.dart';
import 'package:graphql/src/websocket/messages.dart';
@@ -77,24 +79,24 @@ class SocketClient {
this.protocols = const <String>[
'graphql-ws',
],
- this.headers = const <String, String>{
- 'content-type': 'application/json',
- },
this.config = const SocketClientConfig(),
+ @visibleForTesting
+ this.randomBytesForUuid,
}) {
_connect();
}
- final Uuid _uuid = Uuid();
+ Uint8List randomBytesForUuid;
final String url;
final SocketClientConfig config;
final Iterable<String> protocols;
- final Map<String, dynamic> headers;
final BehaviorSubject<SocketConnectionState> _connectionStateController =
BehaviorSubject<SocketConnectionState>();
Timer _reconnectTimer;
WebSocket _socket;
+ @visibleForTesting
+ WebSocket get socket => _socket;
Stream<GraphQLSocketMessage> _messageStream;
StreamSubscription<ConnectionKeepAlive> _keepAliveSubscription;
@@ -114,7 +116,6 @@ class SocketClient {
try {
_socket = await WebSocket.connect(url,
protocols: protocols,
- headers: headers,
);
_connectionStateController.value = SocketConnectionState.CONNECTED;
print('Connected to websocket.');
@@ -122,7 +123,6 @@ class SocketClient {
_messageStream = _socket
.stream
- .asBroadcastStream()
.map<GraphQLSocketMessage>(_parseSocketMessage);
if (config.inactivityTimeout != null) {
@@ -256,7 +256,7 @@ class SocketClient {
/// In case of socket disconnection, the returned stream will be closed.
Stream<SubscriptionData> subscribe(
final SubscriptionRequest payload, final bool waitForConnection) {
- final String id = _uuid.v4();
+ final String id = Uuid.randomUuid(random: randomBytesForUuid).toString();
final StreamController<SubscriptionData> response =
StreamController<SubscriptionData>();
StreamSubscription<SocketConnectionState> sub;

MODIFY
packages/graphql/lib/src/websocket/messages.dart
packages/graphql/lib/src/websocket/messages.dart
@@ -189,6 +189,13 @@ class SubscriptionData extends GraphQLSocketMessage {
'data': data,
'errors': errors,
};
+
+ @override
+ int get hashCode => toJson().hashCode;
+
+ @override
+ bool operator ==(dynamic other) =>
+ other is SubscriptionData && jsonEncode(other) == jsonEncode(this);
}
/// Errors sent from the server to the client if the subscription operation was

MODIFY
packages/graphql/pubspec.yaml
packages/graphql/pubspec.yaml
@@ -14,10 +14,10 @@ dependencies:
mime: ^0.9.6+2
path: ^1.6.2
http_parser: ^3.1.3
- uuid: ^2.0.1
+ uuid_enhanced: ^3.0.2
graphql_parser: ^1.1.3
rxdart: ^0.22.0
- websocket: ^0.0.2
+ websocket: ^0.0.3
dev_dependencies:
pedantic: <=1.7.99
mockito: ^4.0.0

MODIFY
packages/graphql/test/socket_client_test.dart
packages/graphql/test/socket_client_test.dart
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:graphql/src/link/operation.dart';
+import 'package:graphql/src/websocket/messages.dart';
import 'package:test/test.dart';
-// import 'dart:html' show WebSocket, querySelector;
import 'package:graphql/src/socket_client.dart'
show SocketClient, SocketConnectionState;
void main() {
group('SocketClient', () {
+ SocketClient socketClient;
+ setUp(() {
+ socketClient = SocketClient(
+ 'ws://echo.websocket.org',
+ protocols: null,
+ randomBytesForUuid: Uint8List.fromList(
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
+ );
+ });
+ tearDown(() async {
+ await socketClient.dispose();
+ });
test('connection', () async {
- final c = SocketClient('ws://echo.websocket.org');
await expectLater(
- c.connectionState.asBroadcastStream(),
+ socketClient.connectionState.asBroadcastStream(),
emitsInOrder(
[
SocketConnectionState.CONNECTING,
@@ -16,7 +31,43 @@ void main() {
],
),
);
- await c.dispose();
- }, tags: "integration");
});
+ test('subscription data', () async {
+ final payload = SubscriptionRequest(Operation(document: 'empty'));
+ final waitForConnection = true;
+ final subscriptionDataStream =
+ socketClient.subscribe(payload, waitForConnection);
+ await socketClient.connectionState
+ .where((state) => state == SocketConnectionState.CONNECTED)
+ .first;
+
+ // ignore: unawaited_futures
+ socketClient.socket.stream
+ .where((message) =>
+ message ==
+ '{"type":"start","id":"01020304-0506-4708-890a-0b0c0d0e0f10","payload":{"operationName":null,"query":"empty","variables":{}}}')
+ .first
+ .then((_) {
+ socketClient.socket.add(jsonEncode({
+ 'type': 'data',
+ 'id': '01020304-0506-4708-890a-0b0c0d0e0f10',
+ 'payload': {
+ 'data': {'foo': 'bar'},
+ 'errors': ['error and data can coexist']
+ }
+ }));
+ });
+
+ await expectLater(
+ subscriptionDataStream,
+ emits(
+ SubscriptionData(
+ '01020304-0506-4708-890a-0b0c0d0e0f10',
+ {'foo': 'bar'},
+ ['error and data can coexist'],
+ ),
+ ),
+ );
+ });
+ }, tags: "integration");
}

MODIFY
packages/graphql_flutter/example/lib/graphql_widget/main.dart
packages/graphql_flutter/example/lib/graphql_widget/main.dart
@@ -26,8 +26,7 @@ class GraphQLWidgetScreen extends StatelessWidget {
getToken: () async => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN',
);
- // TODO don't think we have to cast here, maybe covariant
- Link link = authLink.concat(httpLink as Link);
+ Link link = authLink.concat(httpLink);
if (ENABLE_WEBSOCKETS) {
final WebSocketLink websocketLink = WebSocketLink(
url: 'ws://localhost:8080/ws/graphql',

test(client): integration test websocket

This test can be run both on `vm` and `chrome`, as now we are using a package to have universal API for websocket.