awslabs / aws-mobile-appsync-sdk-js

JavaScript library files for Offline, Sync, Sigv4. includes support for React Native
Apache License 2.0
921 stars 267 forks source link

`aws-appsync-auth-link` + Next.js produces `TypeError: forward is not a function` error #473

Closed sakhmedbayev closed 5 years ago

sakhmedbayev commented 5 years ago

Do you want to request a feature or report a bug? bug

What is the current behavior? Following this example, I am trying to initialize apollo client using Next.js. Here is an example I follow to make it work except my lib/initApolloClient.ts looks like the following:

import { awsmobile } from "@teloscom/ecomapiawsamp";
import { NormalizedCacheObject } from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { ApolloLink } from "apollo-link";
import Amplify, { Auth } from "aws-amplify";
import { AUTH_TYPE } from "aws-appsync";
import { createAuthLink } from "aws-appsync-auth-link";
import fetch from "isomorphic-unfetch";

Amplify.configure({
  ...awsmobile
});

let apolloClient: ApolloClient<any> | null = null;

// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
  // @ts-ignore
  global.fetch = fetch;
}

function createApolloClient(initialState: NormalizedCacheObject) {
  const ssrMode = !process.browser

  const url = awsmobile.aws_appsync_graphqlEndpoint;
  const region = awsmobile.aws_appsync_region;
  const auth: any = {
    type: AUTH_TYPE.AWS_IAM,
    credentials: () => Auth.currentCredentials()
  }

  const link = ApolloLink.from([
    createAuthLink({ url, region, auth }),
  ]);

  const client = new ApolloClient({
    link,
    cache: new InMemoryCache(),
    ssrMode
  });

  if (initialState) {
    client.cache = new InMemoryCache().restore(initialState);
  }

  return client;
}

export default function initApollo(initialState: any = {}) {
  // Make sure to create a new client for every server-side request so that data
  // isn't shared between connections (which would be bad)
  if (!process.browser) {
    return createApolloClient(initialState);
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = createApolloClient(initialState);
  }

  return apolloClient;
}

All the above produces the following error:

(node:19313) UnhandledPromiseRejectionWarning: TypeError: forward is not a function
    at Object.<anonymous> (/Users/np/mine/telos/ecomcntrls/node_modules/aws-appsync-auth-link/lib/auth-link.js:154:43)
    at step (/Users/np/mine/telos/ecomcntrls/node_modules/aws-appsync-auth-link/lib/auth-link.js:56:23)
    at Object.next (/Users/np/mine/telos/ecomcntrls/node_modules/aws-appsync-auth-link/lib/auth-link.js:37:53)
    at fulfilled (/Users/np/mine/telos/ecomcntrls/node_modules/aws-appsync-auth-link/lib/auth-link.js:28:58)
    at process._tickCallback (internal/process/next_tick.js:68:7)
(node:19313) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 4)

Which versions and which environment (browser, react-native, nodejs) / OS are affected by this issue? Did this work in previous versions?

here is my complete package.json file:

{
  "name": "with-typescript",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "type-check": "tsc"
  },
  "dependencies": {
    "@apollo/react-ssr": "^3.1.3",
    "@teloscom/ecomapiawsamp": "^1.0.12", // this is private, you cannot install it
    "apollo-boost": "^0.4.4",
    "apollo-cache-inmemory": "^1.6.3",
    "apollo-client": "^2.6.4",
    "apollo-link": "^1.2.13",
    "aws-amplify": "^1.2.2",
    "aws-appsync": "^2.0.1",
    "aws-appsync-auth-link": "^1.0.1",
    "isomorphic-unfetch": "3.0.0",
    "next": "latest",
    "react": "^16.10.1",
    "react-apollo-hooks": "^0.5.0",
    "react-dom": "^16.10.1"
  },
  "devDependencies": {
    "@types/next": "^8.0.6",
    "@types/node": "^12.7.8",
    "@types/react": "^16.9.3",
    "@types/react-dom": "^16.9.1",
    "typescript": "3.6.3"
  },
  "license": "ISC"
}
sakhmedbayev commented 5 years ago

This issue can be resolved if you add createHttpLink({ uri: url }), to ApolloLink config. The doc here is incorrect.

dai-shi commented 5 years ago

As I understand:

1) this works

const link = ApolloLink.from([
  createAuthLink({ url, region, auth }),
  createSubscriptionHandshakeLink(url, httpLink)
]);

2) this doesn't

const link = ApolloLink.from([
  createAuthLink({ url, region, auth }),
]);

3) but this works

const link = ApolloLink.from([
  createAuthLink({ url, region, auth }),
  httpLink,
]);

This is reasonable if you know the link is split in createSubscriptionHandshakeLink.

sakhmedbayev commented 5 years ago

Yep, you are right. My fault. Thanks, @dai-shi!

sakhmedbayev commented 5 years ago

@dai-shi, perhaps you can help me here. I am trying to initialize apollo-client as I described above. Everything works client-side but not on the server. I get this error:

Error while running `getMarkupFromTree` { Error: Network error: Response not successful: Received status code 401
    at new ApolloError (/Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-client/bundle.umd.js:92:26)
    at /Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-client/bundle.umd.js:1587:34
    at /Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-client/bundle.umd.js:2007:15
    at Set.forEach (<anonymous>)
    at /Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-client/bundle.umd.js:2005:26
    at Map.forEach (<anonymous>)
    at QueryManager.broadcastQueries (/Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-client/bundle.umd.js:2003:20)
    at /Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-client/bundle.umd.js:1482:29
    at process._tickCallback (internal/process/next_tick.js:68:7)
  graphQLErrors: [],
  networkError:
   { ServerError: Response not successful: Received status code 401
       at Object.exports.throwServerError (/Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-link-http-common/lib/index.js:23:17)
       at /Users/saidakhmedbayev/np/mine/telos/ecomcntrls/node_modules/apollo-link-http-common/lib/index.js:48:21
       at process._tickCallback (internal/process/next_tick.js:68:7)
     name: 'ServerError',
     response:
      Response {
        size: 0,
        timeout: 0,
        [Symbol(Body internals)]: [Object],
        [Symbol(Response internals)]: [Object] },
     statusCode: 401,
     result: { errors: [Array] } },
  message:
   'Network error: Response not successful: Received status code 401',
  extraInfo: undefined }
[ event ] disposing inactive page(s): /next/dist/pages/_error
[ info ]  bundled successfully, waiting for typecheck results ...
[ ready ] compiled successfully - ready on http://localhost:3000

and here is how my withApollo.tsx looks like:

import React from "react";
import initApollo from "./initApollo";
import Head from "next/head";
import { renderToString } from "react-dom/server";
// import { NextAppContext } from "next";
import { ApolloClient } from "apollo-boost";
import { getMarkupFromTree } from "react-apollo-hooks";

// import { getDataFromTree } from "@apollo/react-ssr";

export default (
  App: React.ComponentType<any> & { getInitialProps?: Function }
) => {
  return class Apollo extends React.Component {
    static displayName = "withApollo(App)";
    apolloClient: ApolloClient<any>;
    static async getInitialProps(ctx: any) {
      const { Component, router } = ctx;

      let appProps = {};
      if (App.getInitialProps) {
        appProps = await App.getInitialProps(ctx);
      }

      // Run all GraphQL queries in the component tree
      // and extract the resulting data
      const apollo = initApollo();
      if (!process.browser) {
        try {
          // Run all GraphQL queries
          await getMarkupFromTree({
            renderFunction: renderToString,
            tree: (
              <App
                {...appProps}
                Component={Component}
                router={router}
                apolloClient={apollo}
              />
            )
          });

          // await getDataFromTree(
          //   <App
          //     {...appProps}
          //     Component={Component}
          //     router={router}
          //     apolloClient={apollo}
          //   />
          // );
        } catch (error) {
          // Prevent Apollo Client GraphQL errors from crashing SSR.
          // Handle them in components via the data.error prop:
          // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
          console.error("Error while running `getMarkupFromTree`", error);
        }

        // getDataFromTree does not call componentWillUnmount
        // head side effect therefore need to be cleared manually
        Head.rewind();
      }

      // Extract query data from the Apollo store
      const apolloState = apollo.cache.extract();

      return {
        ...appProps,
        apolloState
      };
    }

    constructor(props: any) {
      super(props);
      this.apolloClient = initApollo(props.apolloState);
    }

    render() {
      return <App {...this.props} apolloClient={this.apolloClient} />;
    }
  };
};

so this part of withApollo.tsx does not work:

 await getMarkupFromTree({
            renderFunction: renderToString,
            tree: (
              <App
                {...appProps}
                Component={Component}
                router={router}
                apolloClient={apollo}
              />
            )
          });
dai-shi commented 5 years ago

Unfortunately, I don't have any clue at first glance. So, it works without appsync auth link, does it?

romainquellec commented 4 years ago

When it's working, can you make a PR on next repo @sakhmedbayev ?