在 React 中使用 ApolloClient 订阅 AppSync

Dr *_*ter 5 reactjs graphql apollo-client aws-appsync

我目前正在使用 ApolloClient 连接到 AppSync GraphQL API。这一切对于查询和突变都非常有效,但我在订阅工作时遇到了一些麻烦。我遵循了 Apollo 文档,我的 App.js 如下所示:

import React from 'react';
import './App.css';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createHttpLink } from 'apollo-link-http';
import AWSAppSyncClient, { AUTH_TYPE } from "aws-appsync";
import { useSubscription } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const url = "https://xxx.appsync-api.eu-west-2.amazonaws.com/graphql"
const realtime_url = "wss://xxx.appsync-realtime-api.eu-west-2.amazonaws.com/graphql"
const region = "eu-west-2";
const auth = {
  type: AUTH_TYPE.API_KEY,
  apiKey: process.env.REACT_APP_API_KEY
};

const wsLink = new WebSocketLink({
  uri: realtime_url,
  options: {
    reconnect: true
  },
});

const link = split(
  // split based on operation type
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  ApolloLink.from([
     createAuthLink({ realtime_url, region, auth }), 
     wsLink
  ]),
  ApolloLink.from([
     createAuthLink({ url, region, auth }), 
     createHttpLink({ uri: url })
  ])
);

const client = new ApolloClient({
  link: link,
  cache: new InMemoryCache({
    dataIdFromObject: object => object.id,
  }),
});

function Page() {
  const { loading, error, data } = useSubscription(
    gql`
      subscription questionReleased {
        questionReleased {
          id
          released_date
        }
      }
    `
  )

  if (loading) return <span>Loading...</span>
  if (error) return <span>Error!</span>
  if (data) console.log(data)

  return (
    <div>{data}</div>
  );
}

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <Page />
      </div>
    </ApolloProvider>
  );
}

export default App;
Run Code Online (Sandbox Code Playgroud)

如果我转到网络检查器中的网络选项卡,我可以看到请求:

wss://xxx.appsync-realtime-api.eu-west-2.amazonaws.com/graphql

和消息:

{"type":"connection_init","payload":{}}
{"id":"1","type":"start","payload":{"variables":{},"extensions":{},"operationName":"questionReleased","query":"subscription questionReleased {\n  questionReleased {\n    id\n    released_date\n    __typename\n  }\n}\n"}}
{"id":"2","type":"start","payload":{"variables":{},"extensions":{},"operationName":"questionReleased","query":"subscription questionReleased {\n  questionReleased {\n    id\n    released_date\n    __typename\n  }\n}\n"}}
{"payload":{"errors":[{"message":"Both, the \"header\", and the \"payload\" query string parameters are missing","errorCode":400}]},"type":"connection_error"}
Run Code Online (Sandbox Code Playgroud)

我已经搜索了很多,似乎 ApolloClient 可能与 AppSync 订阅不兼容 - 有没有人能够确认这一点?

因此,作为替代方案,我尝试将其AWSAppSyncClient用于订阅:

function Page() {
  const aws_client = new AWSAppSyncClient({
    region: "eu-west-2",
    url: realtime_url,
    auth: {
      type: AUTH_TYPE.API_KEY,
      apiKey: process.env.REACT_APP_API_KEY
    },
    disableOffline: true
  });

  const { loading, error, data } = useSubscription(
    gql`
      subscription questionReleased {
        questionReleased {
          id
          released_date
        }
      }
    `,
    {client: aws_client}
  )

  if (loading) return <span>Loading...</span>
  if (error) return <span>Error!</span>
  if (data) console.log(data)

  return (
    <div>{data}</div>
  );
}
Run Code Online (Sandbox Code Playgroud)

它现在随请求发送查询字符串:

wss://xxx.appsync-realtime-api.eu-west-2.amazonaws.com/graphql?header=eyJob3N0I...&payload=e30=

我现在得到一个不同的错误:

{"type":"connection_init"}
{"payload":{"errors":[{"errorType":"HttpNotFoundException"}]},"type":"connection_error"}
Run Code Online (Sandbox Code Playgroud)

我已经仔细检查了 url 并且没问题(如果不是你得到的话ERR_NAME_NOT_RESOLVED)。当我通过 AppSync 控制台手动运行订阅时,订阅工作,所以这也应该没问题。

我也试过.hydrated()了,aws_client但又出现了一个错误 ( TypeError: this.refreshClient(...).client.subscribe is not a function)

我究竟做错了什么?这让我疯狂了几天!

Dr *_*ter 9

我终于在发布后不久想通了。我会把解决方案放在这里,以防其他人遇到同样的问题。首先AWSAppSyncClient应该采用主graphql url而不是实时url - 它可以计算出实时url本身。但是,我仍然无法使用useSubscription钩子进行此操作,只能通过调用aws_client.subscribe().

为了让它与useSubscription钩子一起工作,我找到了这个讨论中提到的解决方案:https : //github.com/awslabs/aws-mobile-appsync-sdk-js/issues/450

具体来说:https : //github.com/awslabs/aws-mobile-appsync-sdk-js#using-authorization-and-subscription-links-with-apollo-client-no-offline-support

相关代码是:

import { createSubscriptionHandshakeLink } from 'aws-appsync-subscription-link';
const httpLink = createHttpLink({ uri: url })
const link = ApolloLink.from([
  createAuthLink({ url, region, auth }),
  createSubscriptionHandshakeLink(url, httpLink)
]);
Run Code Online (Sandbox Code Playgroud)

使用后,一切对我来说都很完美。

  • 此内容应于 2021 年更新,以反映 AppSync 弃用的 MQTT 支持。`createSubscriptionHandshakeLink(url, httpLink)` 将不再有效,您必须使用 `createSubscriptionHandshakeLink({ url,region, auth })` (5认同)