aws-mobile-appsync-sdk-js icon indicating copy to clipboard operation
aws-mobile-appsync-sdk-js copied to clipboard

Using HttpLink from apollo-angular-link-http

Open fcobia opened this issue 7 years ago • 5 comments

I am creating an Angular 6 app and I am trying to get server side rendering working. I am having a lot of trouble with getting the page to wait for the GraphQL calls to AppSync to finish before the page is rendered.

I suspected that the calls were being made outside the zone.js framework and so Angular could not detect when the calls were finished. And finally today I came across this which under the section titled "Server-side rendering" says that you must use HttpLink from apollo-angular-link-http for GraphQL to work correctly with zone.js.

So I looked at the source to aws-appsync and this issue to configure the AWSAppsyncClient to use the HttpLink from apollo-angular-link-http. See the code below.

However, this causes the GraphQL calls to fail. When I try in a web browser I simply get the following error and there is no call to app sync listed in the network tab of the Chrome browser:

ERROR Error: Uncaught (in promise): Error: Network error: undefined

When I run the server side code it give the same error but it shows a stack trace that gives a little more information. Down in the stack trace it has:

graphQLErrors: [], networkError: { body: [Object], url: 'https://XXXXXXXXXXXX.appsync-api.us-east-1.amazonaws.com/graphql', headers: [HttpHeaders], status: 404, statusText: 'Not Found' }, message: 'Network error: undefined', extraInfo: undefined }

How can I configure the AWSAppSyncClient to use the HttpLink so that zones works correctly?

Here is the code I use to create the AWSAppSync Client with the creation code that works (minus zones) commented out. Please excuse the messy code. I have been doing a lot of experimenting.

import AWSAppSyncClient, {createAppSyncLink} from 'aws-appsync';
import {environment} from "../../../environments/environment";
import {NormalizedCache} from "apollo-cache-inmemory";
import { ISignUpResult, CognitoUser, MFAOption, CognitoUserSession, CognitoUserAttribute } from 'amazon-cognito-identity-js';
import Auth from "@aws-amplify/auth";
import {Injectable} from '@angular/core';
import {HttpLink, HttpLinkModule} from 'apollo-angular-link-http';
import { onError } from 'apollo-link-error';
import {ApolloLink} from 'apollo-link';



export enum AppSyncServerType {
	UnknownUser	= "UnknownUser",
	KnownUser	= "KnownUser"
}


@Injectable({providedIn: 'root'})
export class AppSyncServerService {
	
	// Static Private Variables
	readonly sharedUnknown: AWSAppSyncClient<NormalizedCache>;
	readonly sharedKnown: AWSAppSyncClient<NormalizedCache>;



	// ========================================
	// Private Methods
	// ========================================
	private async currentJwtToken(): Promise<string> {
		const session: CognitoUserSession = await Auth.currentSession();
		
		return session.getIdToken().getJwtToken();
	}
	
	private newClient(type: AppSyncServerType): AWSAppSyncClient<NormalizedCache> {
		const env = environment.appsync[type];
		
		// Create the auth config
		const authConfig: any = {};
		authConfig.type = env.authenticationType;
		switch (env.authenticationType) {

			case 'AWS_IAM':
				authConfig.credentials = () => Auth.currentCredentials();
				break;

			case 'AMAZON_COGNITO_USER_POOLS':
				authConfig.jwtToken = () => this.currentJwtToken();
				break;
		}

		const apolloHttpLink = this.httpLink.create({uri: env.endpoint});

		// return new AWSAppSyncClient({
		// 	url: env.endpoint,
		// 	region: env.region,
		// 	auth: authConfig,
		// 	disableOffline: true,
		// });


		const onErrorLink = onError(({ graphQLErrors, networkError }) => {
			if (graphQLErrors) {
				graphQLErrors.map(({message, locations, path}) =>
					console.log(
						`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
					)
				);
			}
			if (networkError) { console.log(`[Network error]: ${networkError}`); }
		});

		const appSyncLink = createAppSyncLink({
			url: env.endpoint,
			region: env.region,
			auth: authConfig,
			complexObjectsCredentials: () => Auth.currentCredentials(),
			resultsFetcherLink: apolloHttpLink,
		});

		const link = ApolloLink.from([
			appSyncLink,
			// apolloHttpLink
		]);

		// @ts-ignore
		return new AWSAppSyncClient({}, { link });
	}



	// ========================================
	// Constructor
	// ========================================
	constructor(private httpLink: HttpLink) {
		this.sharedUnknown = this.newClient(AppSyncServerType.UnknownUser);
		this.sharedKnown = this.newClient(AppSyncServerType.KnownUser);
	}
}

fcobia avatar Oct 03 '18 04:10 fcobia

Hi @fcobia

And finally today I came across this which under the section titled "Server-side rendering" says that you must use HttpLink from apollo-angular-link-http for GraphQL to work correctly with zone.js.

Something like this might work:

const apolloHttpLink = httpLink.create({uri: env.endpoint}); // from apollo-angular-link-http

const appSyncLink = createAppSyncLink({
  url: env.endpoint,
  region: appSyncConfig.region,
  auth: {
    type: appSyncConfig.authenticationType,
    apiKey: appSyncConfig.apiKey
  },
  resultsFetcherLink: apolloHttpLink
});

const client = new AWSAppSyncClient({
    disableOffline: true
}, { link , ssrMode: true});

Can you try it and let us know how it goes?

manueliglesias avatar Feb 04 '19 23:02 manueliglesias

@manueliglesias Thank you. It seems to work. I had to modify the code you gave, because it doesn't compile, but I think I got it. The modified code is below. However, there appears to be a lot of duplicate config information, because both the createAppSyncLink and new AWSAppSyncClient calls seem to require all the same information. Is this correct? Is there a way to not duplicate all that information?

Also, and this may be completely unrelated to this code, but when I use this code I started getting 'Refused to set unsafe header "host"' errors in the console whenever a GraphQL call is made. Do you know why this is happening?

		const apolloHttpLink = this.httpLink.create({uri: env.endpoint}); // from apollo-angular-link-http

		const appSyncLink = createAppSyncLink({
			url: env.endpoint,
			region: env.region,
			auth: authConfig,
			complexObjectsCredentials: null,
			resultsFetcherLink: apolloHttpLink
		});

		return new AWSAppSyncClient({
			url: env.endpoint,
			region: env.region,
			auth: authConfig,
			disableOffline: true,
			offlineConfig: {
				storage: localForage,
			},
		}, { link: appSyncLink , ssrMode: true});

fcobia avatar Feb 07 '19 04:02 fcobia

@fcobia hey, did you come to a conclusion about why the error appears in the console? I have done the same stuff you did, and can not seem to be able to get rid of the error :(.

papagei-ma avatar Jan 23 '22 10:01 papagei-ma

It has been a while and I don't remember. I am sure I must have because the project does work now. However, I don't remember what I did.

fcobia avatar Jan 24 '22 00:01 fcobia

@fcobia no problem, thanks anyway. However, I am trying to get rid of aws-appsync-auth-link aws-appsync-subscription-link By copy pasting the whole code without the place where they are setting the host, path, ... pretty much the unsafe headers.

papagei-ma avatar Jan 24 '22 04:01 papagei-ma