aws appsync 订阅不适用于 flutter graphql_flutter 包

aws appsync subscription not working with flutter graphql_flutter package

我的pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter
  graphql_flutter: ^4.0.0

roomctrl.dart

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:innospace/config/appsync-service.dart';
import 'package:innospace/config/client_provider.dart';

class RoomCtrl extends StatefulWidget {
  RoomCtrl({Key key}) : super(key: key);

  _RoomCtrlState createState() => _RoomCtrlState();
}

class _RoomCtrlState extends State<RoomCtrl> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ClientProvider(
        child: Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
         Subscription(
        options:SubscriptionOptions(
            document: gql(AppSyncService.onUpdateStateTestSubscription)),
          builder: (result) {
            if (result.hasException) {
              return Text(result.exception.toString());
            }

            if (result.isLoading) {
              return Center(
                child: const CircularProgressIndicator(),
              );
            }
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  Container(
                    child: Text(result.data.length.toString()),
                  )
                ],
              ),
            );
          },
        )

          ],
        ),
      ),
    ));
  }
}

client_provider.dart

import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:flutter/material.dart';

import 'constants.dart';

String uuidFromObject(Object object) {
  if (object is Map<String, Object>) {
    final String typeName = object['__typename'] as String;
    final String id = object['id'].toString();
    if (typeName != null && id != null) {
      return <String>[typeName, id].join('/');
    }
  }
  return null;
}

ValueNotifier<GraphQLClient> clientFor() {
   const dynamic headers = {
    "headers": {
      "host": AWS_APP_SYNC_ENDPOINT_AUTHORITY,
      "x-api-key": AWS_APP_SYNC_KEY
    }};
   const  sClient= SocketClientConfig(
      autoReconnect : true,
      initialPayload: headers
  );

   final WebSocketLink _webSocketLink =new WebSocketLink(AWS_APP_SYNC_ENDPOINT_WSS,  config:sClient );

   final Link link = _webSocketLink;
  return ValueNotifier<GraphQLClient>(
    GraphQLClient(
      cache: GraphQLCache(),
      link: link,
    ),
  );
}

/// Wraps the root application with the `graphql_flutter` client.
/// We use the cache for all state management.
class ClientProvider extends StatelessWidget {
  ClientProvider({
    @required this.child,
  }) : client = clientFor();

  final Widget child;
  final ValueNotifier<GraphQLClient> client;

  @override
  Widget build(BuildContext context) {
    return GraphQLProvider(
      client: client,
      child: child,
    );
  }
}

constants.dart

[https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html][1]

const AWS_APP_SYNC_ENDPOINT_AUTHORITY = "xxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-southeast-2.amazonaws.com"; 
const AWS_APP_SYNC_ENDPOINT_WSS = "wss://xxxxxxxxxxxxxxxxxxxxx.appsync-realtime-api.ap-southeast-2.amazonaws.com/graphql?header=base64encryption(xxx-xxxxxxxxxxxxxxxxxx as per aws)&payload=e30="; 
const AWS_APP_SYNC_KEY = "xxx-xxxxxxxxxxxxxxxxxx";

订阅查询

static String onUpdateStateTestSubscription = '''
        subscription OnUpdateStateTest {
        onUpdateStateTest {
          __typename
          RoomId
          RoomName
        }
      }''';

控制台

Connecting to websocket: wss://xxxxxxxxxxxxxxxxxxxxx.appsync-realtime-api.ap-southeast-2.amazonaws.com/graphql?header=base64encryption(xxx-xxxxxxxxxxxxxxxxxx as per aws)&payload=e30=...
I/flutter ( 9942): Connected to websocket.
I/flutter ( 9942): Haven't received keep alive message for 30 seconds. Disconnecting..
I/flutter ( 9942): Disconnected from websocket.
I/flutter ( 9942): Scheduling to connect in 5 seconds...
I/flutter ( 9942): Connecting to websocket: wss://xxxxxxxxxxxxxxxxxxxxx.appsync-realtime-api.ap-southeast-2.amazonaws.com/graphql?header=base64encryption(xxx-xxxxxxxxxxxxxxxxxx as per aws)&payload=e30=...
I/flutter ( 9942): Connected to websocket.
I/flutter ( 9942): Haven't received keep alive message for 30 seconds. Disconnecting..
I/flutter ( 9942): Disconnected from websocket.
I/flutter ( 9942): Scheduling to connect in 5 seconds...

最终移动设备上的输出显示正在加载 gif,即 CircularProgressIndicator(),意味着 returns 在“if (result.isLoading)”

时始终为真

[![在此处输入图片描述][2]][2]

谁能帮忙!!

注意:相同的应用程序同步在 angular 应用程序中完美运行。 [1]: https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html [2]: https://i.stack.imgur.com/59KZh.png

在底部查看我最近的更新。

我实际上 运行 遇到了同样的问题。我没有完整的答案,但我觉得这是部分答案的线索。

From the link:

As far as I know, AppSync doesn't use wss://<your_end_point>. I have tried this before, and my http connection was never "upgraded to a wss" connection. I had to POST a request to the http endpoint requesting a subscription connection, then AWS returned connection details containing a new endpoint (a wss url), a topic and a client id. Only then could I connect to the socket using using a third party library (using the URL AWS returned). I had to dig into their official sdk to find this out, and then through trial and error to get it to work.

我已经使用 GraphiQL 工具确认,当我 POST 我的订阅 graphql blob 时,我确实得到了响应。我得到了一个 websocket url 以及 JSON 中的一些其他项目。我还没有弄清楚如何在 Flutter 中捕获该响应,但如果我也找到那部分内容,我会更新它。

从那里开始,我相信我们需要从那个新 url 动态创建 websocket 并将我们的订阅 graphql blob 发送到它。

当然,这很复杂,但这是我现在要走的路。

更新:AWS Amplify for Flutter 现在支持订阅!我正在使用混合解决方案。 mutations/queries 我有 flutter_graphqlartemis。对于订阅,我使用 amplify_api: '<1.0.0'。它刚刚在过去几周发布。实际上我只花了几分钟就让它工作了。 Amplify 会自动为您进行 URL 解析和身份验证。

Link to official docs

您需要生成特定于您的 AWS 端点的 amplifyconfiguration.dart 并将其添加到您的项目中。 AWS 文档对此进行了介绍,设置您的 AWS 端点的人员应该确切地知道您需要什么。

示例来自 link:

try {
    String graphQLDocument = '''subscription OnCreateTodo {
        onCreateTodo {
          id
          name
          description
        }
      }''';

    var operation = Amplify.API.subscribe(
        request: GraphQLRequest<String>(document: graphQLDocument),
        onData: (event) {
          print('Subscription event data received: ${event.data}');
        },
        onEstablished: () {
          print('Subscription established');
        },
        onError: (e) {
          print('Subscription failed with error: $e');
        },
        onDone: () {
          print('Subscription has been closed successfully');
        });
} on ApiException catch (e) {
    print('Failed to establish subscription: $e');
}

别忘了退订:

// Cancel the subscription when you're finished with it
operation.cancel();

我在这里尝试的第一件事是使用@JLuisRojas 的 custom AppSync request serializer:

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

  const AppSyncRequest({
    this.authHeader,
  });

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

// ...

final token = session.getAccessToken().getJwtToken();

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

final authHeader = {
  "Authorization": token,
  "host": "$apiId.appsync-api.$zone.amazonaws.com",
};

final encodedHeader = toBase64(authHeader);

final WebSocketLink wsLink = WebSocketLink(
  'wss://$apiId.appsync-realtime-api.$zone.amazonaws.com/graphql?header=$encodedHeader&payload=e30=',
  config: SocketClientConfig(
    serializer: AppSyncRequest(authHeader: authHeader),
    inactivityTimeout: Duration(seconds: 60),
  )
);

final AuthLink authLink = AuthLink(
  getToken: () => token,
);

final Link link = authLink.concat(wsLink);

请注意,连接似乎不仅需要 header query parameter, but it also requires authorization in the extensions field on each request, and a non-standard encoding structure (docs)。