Chris Tim
Chris Tim

Reputation: 31

DuplicatedOperationError when creating Graphql Subscription from flutter to AWS AppSync

I have a flutter frontend where I use graphql_flutter to connect to an AWS AppSync graphql API. Queries and Mutations work exactly as expected. I am also able to establish a WebSocket connection for a subscription, but when I(graphql_flutter) send the start message:

{
  "type": "start",
  "id": "061bf284-c738-4a83-96e9-c8cf4b17369e",
  "payload": {
    "data": "{\"query\":\"subscription TransportChanged {\n  transportChange {\n    id\n    __typename\n  }\n  __typename\n}\",\"variables\":{}}",
    "extensions": {
      "authorization": {
        "Authorization": "base64encodedAuthorizationHeader",
        "host": "actual_api.appsync-api.eu-central-1.amazonaws.com"
      }
    }
  }
}

~ 

to subscribe I receive

{
  "id": "061bf284-c738-4a83-96e9-c8cf4b17369e",
  "type": "error",
  "payload": {
    "errors": [
      {
        "errorType": "DuplicatedOperationError",
        "message": "Duplicated operation with id 061bf284-c738-4a83-96e9-c8cf4b17369e"
      }
    ]
  }
}

and then

{
  "id": "061bf284-c738-4a83-96e9-c8cf4b17369e",
  "type": "start_ack"
}

But I don't receive any events over the connection. I have tried out to connect to the subscription through the AWS AppSync QueryConsole and it works, so it must be something with my local setup.

Graphql is configured like this to work with the AppSync API:


    var authSession = context.read<AppUser>().getAuthSessionFromState();
    final String? authenticationToken =
        context.watch<AppUser>().authenticationToken;
    Link link;
    final HttpLink httpLink = HttpLink(
      graphqlUrl,
    );

    if (authenticationToken != null && authenticationToken.isNotEmpty) {
      final AuthLink authLink = AuthLink(
          getToken: () async => authenticationToken,
          headerKey: 'Authorization');
      graphQLClientIsAuthenticated = true;
      currentToken = authenticationToken;
      _tokenExpiry = authSession?.userPoolTokens?.idToken.claims.expiration;

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

      final authHeader = {
        "Authorization": authenticationToken,
        "host": graphqlHost,
      };

      final encodedHeader = toBase64(authHeader);

      final WebSocketLink wsLink = WebSocketLink(
        "$graphqlRealTimeUrl?header=$encodedHeader&payload=e30=",
        config: SocketClientConfig(
          serializer: AppSyncRequest(
              authHeader: authHeader),
          inactivityTimeout: const Duration(seconds: 60),
        ),
      );
      link = Link.split((request) => request.isSubscription,
          authLink.concat(wsLink), authLink.concat(httpLink));
    } else {
      link = httpLink;
    }

The AppSyncRequest looks like this:

import 'dart:convert';

import 'package:gql/language.dart' show printNode;
import 'package:graphql_flutter/graphql_flutter.dart';

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.isEmpty?null: request.variables,
        }),
        "extensions": {
          "authorization": this.authHeader,
        }
      };
}

I have seen another question about DuplicatedOperationError that was solved by the owner as being a network issue. AWS AppSync - Duplicated Operation Error - subscriptions not working This is not the case for me.

Upvotes: 1

Views: 193

Answers (1)

Chris Tim
Chris Tim

Reputation: 31

While researching the question I tried it out from the AppSync Console again and found out that there was one single difference between my own request and the one from AWS console: My Request was generated by graphql_codegen package and included two "__typename" return items. these apparently caused an issue on AWS AppSync side. So i extended my build.yml with this:

targets:   $default:
    builders:
      graphql_codegen:
        options:
          addTypenameExcludedPaths:
            - subscription

And now the request is working as expected.

Upvotes: 2

Related Questions