Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amplify Subscriptions support #1163

Open
ZRunner opened this issue Jun 16, 2022 · 14 comments
Open

Amplify Subscriptions support #1163

ZRunner opened this issue Jun 16, 2022 · 14 comments
Assignees
Labels
Priority: High High priority to include it inside next release ⚡ websocket Web Socket Related
Milestone

Comments

@ZRunner
Copy link

ZRunner commented Jun 16, 2022

I cannot seem to be able to use GraphQL subscriptions with my AWS server, by using Cognito authentication.
Queries and mutations work fine as far as I can tell, but I couldn't find how to connect to the amplify websocket.

Here is the code I tried, based on this comment: #682 (comment) (which I had to edit because Cognito tokens are not static)

import 'dart:async';
import 'dart:convert';

import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:flutter/material.dart';
import 'package:gql/language.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

String toBase64(Map data) => base64.encode(utf8.encode(jsonEncode(data)));

const apiId = "XXX";
const zone = "eu-west-1";

class AppSyncRequest extends RequestSerializer {
  final Map<String, dynamic> authHeader;

  const AppSyncRequest({
    required this.authHeader,
  });

  @override
  Map<String, dynamic> serializeRequest(Request request) => {
        "data": jsonEncode({
          "query": printNode(request.operation.document),
          "variables": request.variables,
        }),
        "extensions": {
          "authorization": authHeader,
        }
      };
}

ValueNotifier<GraphQLClient> buildClient() {
  Future<String?> _fetchSession() async {
    try {
      final session = await Amplify.Auth.fetchAuthSession(
              options: CognitoSessionOptions(getAWSCredentials: true))
          as CognitoAuthSession;
      if (!session.isSignedIn) {
        return null;
      }
      return session.userPoolTokens?.accessToken;
    } on AuthException catch (e) {
      debugPrintStack(label: e.toString());
    }
    return null;
  }

  Future<String?> _getIdToken() async {
    try {
      final session = await Amplify.Auth.fetchAuthSession(
              options: CognitoSessionOptions(getAWSCredentials: true))
          as CognitoAuthSession;
      if (!session.isSignedIn) {
        return null;
      }
      return session.userPoolTokens?.idToken;
    } on AuthException catch (e) {
      debugPrintStack(label: e.toString());
    }
    return null;
  }

  final HttpLink httpLink =
      HttpLink("https://$apiId.appsync-api.$zone.amazonaws.com/graphql");

  final WebSocketLink wsLink = WebSocketLink(
    'wss://$apiId.appsync-realtime-api.$zone.amazonaws.com/graphql',
    config: SocketClientConfig(
      initialPayload: () async {
        final token = await _fetchSession();
        return {
          "headers": {
            "Authorization": '$token',
            "host": "$apiId.appsync-api.$zone.amazonaws.com",
          },
        };
      },
    ),
  );

  final AuthLink authLink = AuthLink(
    getToken: _fetchSession,
  );

  // final Link link = authLink.concat(httpLink).concat(wsLink);
  final Link link = Link.split((request) => request.isSubscription,
      authLink.concat(wsLink), authLink.concat(httpLink));

  ValueNotifier<GraphQLClient> client = ValueNotifier(
    GraphQLClient(
      link: link,
      defaultPolicies:
          DefaultPolicies(query: Policies(fetch: FetchPolicy.cacheAndNetwork)),
      cache: GraphQLCache(
        partialDataPolicy: PartialDataCachePolicy.accept,
        store: HiveStore(),
      ),
    ),
  );
  return client;
}

class ClientProvider extends StatefulWidget {
  final Widget child;

  const ClientProvider({
    Key? key,
    required this.child,
  }) : super(key: key);

  @override
  State<ClientProvider> createState() => _ClientProviderState();
}

class _ClientProviderState extends State<ClientProvider> {
  @override
  Widget build(BuildContext context) {
    return GraphQLProvider(
      client: buildClient(),
      child: widget.child,
    );
  }
}
  • If I use the Link.split() method, my console is spammed with flutter: Disconnected from websocket.
  • If I use the Link.concat() method, I get a HttpLinkServerException: GraphQLError(message: Subscriptions over MQTT is not supported., locations: null, path: null, extensions: null)

Naturally in both ways my Subscription widget doesn't work (infinite loading state).

Device / execution context

  • Executed on macOS 12.4 (MacBook Pro M1 Pro)
  • iOS 15.5
  • Flutter 3.0.2
  • graphql_flutter 5.1.0
  • amplify_flutter 0.5.1
  • amplify_api 0.5.1
  • XCode 13.4.1
  • VSCode 1.68.1
@vincenzopalazzo vincenzopalazzo self-assigned this Jun 16, 2022
@vincenzopalazzo vincenzopalazzo added the Priority: Waiting to be assigned To be inside the next release process, it need to be marked with some level of priority label Jun 16, 2022
@vincenzopalazzo
Copy link
Collaborator

Mh! I'm new with amplify but I can reproduce it maybe this weekend.

Sorry about that!

@vincenzopalazzo vincenzopalazzo added this to the v5.2.0 milestone Jun 16, 2022
@matthintosh
Copy link

+1
It will be very useful for using your lib in flutter instead of AWS Amplify which is very far from your implementation of GraphQL...

@vincenzopalazzo
Copy link
Collaborator

We are evaluating on how to manage this issue, I will work on it 🫡

@vincenzopalazzo vincenzopalazzo added Priority: Medium Medium priority to include it inside the next release and removed Priority: Waiting to be assigned To be inside the next release process, it need to be marked with some level of priority labels Jun 22, 2022
@vincenzopalazzo
Copy link
Collaborator

We have a problem with ws right now, and we are introducing integration testing, after this, if the problem is not solved we will build an Amplify to reproduce and work on this problem!

Thanks to report it

@vincenzopalazzo vincenzopalazzo added ⌛ reproduction needed Issue is subtle and requires a true accessible reproduction to debug ⚡ websocket Web Socket Related labels Jun 28, 2022
@temirlanalmassov
Copy link

 final httpLink = HttpLink(AppConstants.urlHttpLink); //'http://somelink.com'
    final wsLink = WebSocketLink(
      AppConstants.urlWsLink, // 'wss://somelink.com/'
      config: SocketClientConfig(
        autoReconnect: true,
        initialPayload: () async {
          final token = await _getRealToken();
          return {
              'authToken': token,
            };
        },
      ),
    );

    final authLink = AuthLink(
      getToken: _getToken,
    );

    final Link link = Link.split(
      (request) => request.isSubscription,
      authLink.concat(wsLink),
      authLink.concat(httpLink),
    );

    _client = GraphQLClient(
      link: link,
      cache: GraphQLCache(
        store: InMemoryStore(),
      ),
    );

This works for me @ZRunner I don't know why it doesn't work with you. Yes it already spam my console about disconnecting, but it reconnects and getting messages

@vincenzopalazzo
Copy link
Collaborator

@temirlanalmassov

Will be useful have the version of the package that you are using

@banaszeknorbert
Copy link

@temirlanalmassov where I can find web socket link?

@temirlanalmassov
Copy link

@temirlanalmassov

Will be useful have the version of the package that you are using

graphql_flutter: ^5.1.0

@temirlanalmassov
Copy link

@temirlanalmassov where I can find web socket link?

I think you can ask it from your BackEnd side.

  static const urlHttpLink = 'https://$domain/graphql/';
  static const urlWsLink = 'wss://$domain/ws/graphql/';

For example I am using it like this

@vincenzopalazzo
Copy link
Collaborator

graphql_flutter: ^5.1.0

@temirlanalmassov I suggest to use the last beta version, and also I'm starting to reproduce the problem with a AWS amplify project I hope to have this up and running soon.

But in the meanwhile, there can you check what happens if you change the protocol that the library is using to initiate the connection?

@vincenzopalazzo
Copy link
Collaborator

@temirlanalmassov I think you are wrong about the API, look to the AWS docs https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#handshake-details-to-establish-the-websocket-connection

In addition, this looks like an error with the old protocol, and I need to make double-check if AWS has a custom protocol or it is based on a well-known protocol.

I was able to reproduce but I will research these days on what protocol is supported by AWS and try to fix this asap.

For the moment it is reproduced in #1206

@vincenzopalazzo vincenzopalazzo added Priority: High High priority to include it inside next release and removed ⌛ reproduction needed Issue is subtle and requires a true accessible reproduction to debug Priority: Medium Medium priority to include it inside the next release labels Aug 21, 2022
@alexboulay
Copy link

@ZRunner Did you get this to work?

@ZRunner
Copy link
Author

ZRunner commented Jun 1, 2023

@ZRunner Did you get this to work?

My company has decided to move to react native due to the lack of maturity of the Flutter ecosystem. So I'm afraid I won't be of any help here, sorry.

@alexboulay
Copy link

alexboulay commented Jun 1, 2023

@ZRunner thats ok! good luck with react native!

i ended up simply creating 2 client instances and it worked fine!

thanks for responding quickly! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: High High priority to include it inside next release ⚡ websocket Web Socket Related
Projects
None yet
Development

No branches or pull requests

6 participants