Graphql-flutter: Subscription with authLink, HttpLink and WebSocketLink

Created on 5 Oct 2019  路  13Comments  路  Source: zino-app/graphql-flutter

I'm trying to subscribe to a query in Hasura, with custom authentication headers.
I'm using HttpLink(for Query/Mutation), WebsocketLink(for subscription) and CustomAuthLink (for custom header)

GraphQLClient: (based on this)
```dart

String subscribeFAQ(){
return """
subscription subscribeFAQ{
faqs {
id
question
answer
}
}
""";

ValueNotifier clientFor ({
@required String uri,
String subscriptionUri,
}) {
final CustomAuthLink _customAuthLink = CustomAuthLink(getHeaders: (){return {"x-hasura-admin-secret":PERSONAL_ACCESS_TOKEN};});

Link link = HttpLink(uri: uri);

if (subscriptionUri != null) {
final WebSocketLink websocketLink = WebSocketLink(
url: subscriptionUri,
config: SocketClientConfig(
autoReconnect: true,
inactivityTimeout: Duration(seconds: 30),
),
);

link = link.concat(websocketLink);

}

link = _customAuthLink.concat(link);

return ValueNotifier(
GraphQLClient(
cache: InMemoryCache(),
link: link,
),
);
}

Custom Auth Link: (based on [this](https://github.com/zino-app/graphql-flutter/issues/173#issuecomment-464435942))
```dart
typedef GetHeaders = Map<String, String> Function();


class CustomAuthLink extends Link {
  CustomAuthLink({
    this.getHeaders,
  }) : super(
    request: (Operation operation, [NextLink forward]) {
      StreamController<FetchResult> controller;

      Future<void> onListen() async {
        try {
          final Map<String, String> headers =  getHeaders();

          operation.setContext(<String, Map<String, String>>{
            'headers': headers
          });
        } catch (error) {
          controller.addError(error);
        }

        await controller.addStream(forward(operation));
        await controller.close();
      }

      controller = StreamController<FetchResult>(onListen: onListen);

      return controller.stream;
    },
  );

  GetHeaders getHeaders;
}

The UI trying to subscribe to the data:

class FAQs extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
      padding: EdgeInsets.all(16),

          child: Subscription<Map<String, dynamic>>(
            'subscribeFAQ',subscribeFAQ(),
            builder: ({dynamic loading, dynamic payload, dynamic error}) {
              if (error != null) {
                return Text(error.toString());
              }

              if (loading == true) {
                return Center(
                  child: const CircularProgressIndicator(),
                );
              }
              print(payload);
              return Center(child: Text('Data Fetched'),);
            },
          )
    ));
  }
}

But I run into an error while trying to subscribe:

{errors: [{extensions: {path: $, code: validation-failed}, message: no subscriptions exist}]}

Most helpful comment

@vishal-android-freak can you try setting up at initPayload as described here and see if help, I think it works with the latest version.

All 13 comments

Banging my head on this for a long time now. Anyone?

Subscription detection is currently based on regex and requires operationName to be set. Could you try to manually set operationName to subscribeFAQ?

{errors: [{extensions: {path: $, code: validation-failed}, message: no subscriptions exist}]}

Looks like a server response. Can you verify that this is returned from the query/mutation endpoint instead of the subscription endpoint?

@klavs if you are using Hasura, authentication error gives the following response from the server when using subscription
{errors: [{extensions: {path: $, code: validation-failed}, message: no subscriptions exist}]}

I'm not sure, but I suspect that the WebSocketLink does not actually use the headers set by AuthLink.

https://github.com/zino-app/graphql-flutter/blob/519381cd1c57515fd26cedb33c68a68d563a6eac/packages/graphql/lib/legacy_socket_api/legacy_socket_link.dart#L8-L27

@klavs how to add the auth headers to websocket? While Apollo does it when creating the WebSocket client object, is there any way for us to do the same? This is super important for our project :(

@vishal-android-freak, I guess you can use the legacy socket link I was referring to earlier as a quick workaround.

@klavs alright. Will do that. Thanks for the help :)

@vishal-android-freak can you try setting up at initPayload as described here and see if help, I think it works with the latest version.

@mainawycliffe that worked! We really need this documented in the Subscription section :) @klavs.

@DrBanner97 I think we can close this!

Hi, initPayload don't work for me, this is my code. i'm make anything wrong?

```dart
class WebSocketTest {
static final WebSocketLink _webSocketLink = WebSocketLink(
url: "ws://192.XXX.XXX.XXX:XXXX/api/socket",
config: SocketClientConfig(
autoReconnect: true,
initPayload: {
"headers": {
"current-tenant": "Test",
"Autorization": "Bearer Test",
"tenant": "Test"
},
"params": {
"tenant": "Test"
}
}
),
);
}

Why is it not working? Any specific errors? And can you try without params, just headers and see if it works.

hi @mainawycliffe, this errors appears on flutter log

I/flutter ( 3045): Connecting to websocket: ws://192.168.50.141:4000/api/socket... I/flutter ( 3045): There was an error causing connection lost: WebSocketException: Connection to 'http://192.168.50.141:4000/api/socket#' was not upgraded to websocket

i was try with only headers like you recommend but not work, my project run on elixir and this is the request info that get from flutter, on my elixir project i need a "tenant" header to know where it have to go the request.

elixir log:
https://paste.ubuntu.com/p/RC78K5PjY4/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

micimize picture micimize  路  16Comments

Zony-Zhao picture Zony-Zhao  路  32Comments

Wisdom0063 picture Wisdom0063  路  26Comments

lordgreg picture lordgreg  路  19Comments

arsen picture arsen  路  17Comments