Open Xocix opened 5 years ago
I am having the same issue!
My workaround is to create my own Rehydrated component that takes in client as a prop. Also i needed to add a resolution to the package.json otherwise it was crashing for another error.
"resolutions": { "apollo-client": "2.6.3" }
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AWSAppSyncClient from 'aws-appsync';
const Rehydrate = props => (
<div
className={`awsappsync ${
props.rehydrated
? 'awsappsync--rehydrated'
: 'awsappsync--rehydrating'
}`}
>
{props.rehydrated ? props.children : <span>Loading...</span>}
</div>
);
class Rehydrated extends Component {
static propTypes = {
render: PropTypes.func,
children: PropTypes.node,
loading: PropTypes.node,
client: PropTypes.instanceOf(AWSAppSyncClient).isRequired,
};
constructor(props) {
super(props);
this.state = {
rehydrated: false,
};
}
async componentWillMount() {
await this.props.client.hydrated();
this.setState({
rehydrated: true,
});
}
render() {
const { render, children, loading } = this.props;
const { rehydrated } = this.state;
if (render) return render({ rehydrated });
if (children) {
if (loading) return rehydrated ? children : loading;
return <Rehydrate rehydrated={rehydrated}>{children}</Rehydrate>;
}
}
}
export default Rehydrated;
This is my custom Rehydrated component.
import React, { useContext, useEffect, useState } from 'react';
import { getApolloContext } from 'react-apollo';
import AWSAppSyncClient from 'aws-appsync';
const Rehydrated = ({ children }) => {
const { client } = useContext(getApolloContext());
const [rehydrated, setState] = useState(false);
useEffect(() => {
if (client instanceof AWSAppSyncClient) {
(async () => {
await client.hydrated();
setState(true);
})();
}
}, [client]);
return rehydrated ? <>{children}</> : null;
};
export default Rehydrated;
Basically the same, but in TypeScript. (might be better to have type guard.)
import { useApolloClient } from '@apollo/react-hooks';
const Rehydrated: React.FC = ({ children }) => {
const client = useApolloClient();
const [rehydrated, setRehydrated] = useState(false);
useEffect(() => {
(async () => {
await (client as AWSAppSyncClient<NormalizedCacheObject>).hydrated();
setRehydrated(true);
})();
}, [client]);
return (
<div className={`awsappsync ${rehydrated ? 'awsappsync--rehydrated' : 'awsappsync--rehydrating'}`}>
{rehydrated ? children : <span>Loading...</span>}
</div>
);
};
This is only a temporary solution. Mutations hang on the client currently.
It won't help as it seems AWSAppSyncClient
diverged too far from ApolloClient
for the rehydration to work. It'll only work w/react-apollo v3 if you disable offline entirely using disableOffline: true
in client's options
@TeoTN - I disabled offline and still got the issue. I bumped to 3.0.1
wonder if that has anything to do w/ it?
@amcdnl well, I forgot to mention that I'm using the Rehydrate from @dai-shi AND disabling offline. I'm pretty sure this is a dumb solution (i.e. combining both, the Rehydrate is not needed probably when offline's off) but it temporarily does the thing.
@TeoTN - Hmm, still didn't work for me. I'll just hold out for the official update (fingers crossed).
@TeoTN - Hmm, still didn't work for me. I'll just hold out for the official update (fingers crossed).
I'm using @dai-shi 's Rehydrate and adding implicit any to my client and seems to work
<ApolloProvider client={client as any}>
<Rehydrated>
<Router />
</Rehydrated>
</ApolloProvider>
I tried this as well last weekend. I got past the rehydrate by writing a separate one but the mutations don't process. They get added to the redux-offline queue but do not execute. AppSync has an offline-link implemented which is defining the effect. I suspect there are things that needs to be sorted at that level. For mutations, I was unable to make it work without fundamentally changing AppSync core. I hope we will see an official adaptation of this soon.
Can AWS tell us what is the plan for supporting Apollo 3.0? Is it in the roadmap? Is there an ETA? @elorzafe @manueliglesias @dabit3
Heres the complete solution I ended up with:
In my package.json
, I have:
{
"dependencies": {
"apollo-client": "^2.6.4",
"apollo-link": "^1.2.12",
"apollo-link-error": "^1.1.11",
"aws-amplify": "^1.1.36",
"aws-amplify-react": "^2.3.12",
"aws-appsync": "^1.8.1",
"react-apollo": "^3.0.1",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"aws-appsync-react": "^1.2.9",
},
"resolutions": {
"apollo-client": "2.6.3"
}
}
then I used one of the Rehydrates
above which looks like:
import React, { useEffect, useState } from 'react';
import { useApolloClient } from 'react-apollo';
import AWSAppSyncClient from 'aws-appsync';
export const Rehydrated = ({ children }) => {
const client = useApolloClient();
const [rehydrated, setState] = useState(false);
useEffect(() => {
if (client instanceof AWSAppSyncClient) {
(async () => {
await client.hydrated();
setState(true);
})();
}
}, [client]);
return rehydrated ? children : null;
};
then my AWS config looks like:
import Amplify from 'aws-amplify';
import { Auth } from 'core/Auth';
import AWSAppSyncClient, { createAppSyncLink } from 'aws-appsync';
import { ApolloLink } from 'apollo-link';
import { onError } from 'apollo-link-error';
import { AWS_CUSTOM_CONFIG } from '../../aws-custom-exports';
Amplify.configure(AWS_CUSTOM_CONFIG);
const onErrorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) {
console.error(`[Network error]: ${networkError}`);
}
});
const auth = {
type: AWS_CUSTOM_CONFIG.aws_appsync_authenticationType as 'OPENID_CONNECT',
jwtToken: () => {
const session = Auth.getStoredSession();
if (session) {
return session.idToken;
}
}
};
export const appSyncClient = new AWSAppSyncClient(
{
url: AWS_CUSTOM_CONFIG.aws_appsync_graphqlEndpoint,
disableOffline: true,
region: AWS_CUSTOM_CONFIG.aws_appsync_region,
auth
},
{
link: ApolloLink.from([
onErrorLink,
createAppSyncLink({
url: AWS_CUSTOM_CONFIG.aws_appsync_graphqlEndpoint,
region: AWS_CUSTOM_CONFIG.aws_appsync_region,
auth,
complexObjectsCredentials: () => Auth.getStoredSession()
})
])
}
);
then I bring it all together in the index.tsx
like:
import React, { Suspense, lazy } from 'react';
import ReactDOM from 'react-dom';
import { appSyncClient, Rehydrated } from 'core/Aws';
import { ApolloProvider } from 'react-apollo';
const Root = () => (
<ApolloProvider client={appSyncClient as any}>
<Rehydrated>
Hello
</Rehydrated>
</ApolloProvider>
);
ReactDOM.render(<Root />, document.getElementById('root'));
The current version of ApolloClient works nicely with this, even the apollo-boost one, with Cognito auth. Just want to get subscriptions on there instead "self-host" MQTT over WS.
let token = await Auth.currentSession().then(session => session.getIdToken().getJwtToken()) // Cognito
let client = new ApolloClient({ uri: awsconfig.aws_appsync_graphqlEndpoint, headers: { 'Authorization': token } }) // Same endpoint
I agree with consensus, latest Apollo Client [v3] now leads web development, and React Native.
We have now explored this issue further. The reason to use AppSync over just the ApolloClient is if you need the offline capability. We wrote our own Rehydrated and got around the issues there but then when posting a mutation, it got enqueued in redux-offline but never processed. We debugged the flow and found the issues to be in the offline-link created redux-offline effect.
AppSync is using a method called client.queryManager.buildOperationForLink
that no longer exists to create the observable and hence nothing happens when redux-offline calls the effect.
We refactored this to the following construct using the same approach as Apollo does in the mutations:
client.queryManager.getObservableFromLink(
mutation,
extraContext,
variables,
false,
).subscribe({
next: data => {...
There were a few other minor issues that we have been changing to make the framework work for our specific use case but this is the key one the core framework falls over on.
We now have this working nicely with hooks and our adapted version of AppSync for our use case and I just wanted to share in case anyone else are looking to do the same or if this could inspire the core team to update the framework as it is a hinderance for adaptation.
We are also stuck here. We are using AppSync with Next.js. The solution found here works only client-side. The server-side cannot go beyond Loading...
stage :-(
Hello everyone - I want to reply and assure you that we have not abandoned support of this project. Per my earlier comment, this is a complex process and to be completely transparent there are things in the Apollo library that are out of our control and there have also been breaking changes in Apollo versions which we're trying to account for, which is also why we pinned the version. We've been spending many hours trying to account for this but it's not simple to solve. After putting more thought into this we've got some initial solutions that I hope will unblock many of you. What we've done is packaged two of the "Links" which are part of the SDK - the Auth and Subscription Links - and published them to NPM. This will allow you to include them in the newer Apollo versions if you wish to use the newer features. This does not include the "Offline Link" for offline support which is more complex to solve and we're looking into for the future.
Installation of the links:
npm install aws-appsync-auth-link aws-appsync-subscription-link
With is I would then give the following recommendations for choosing your client strategy:
Please let us know if you have any trouble using these links or if there is a scenario which we haven't accounted for here.
Thanks for giving us updates on this @undefobj . For offline support, what about the solution of @doldyn ?
Thanks for giving us updates on this @undefobj . For offline support, what about the solution of @doldyn ?
The problem is query manager uses some private methods, and when you factor in the new Local State features of Apollo which can override the cache there are several scenarios which can lead to inconsistent state of the underlying persisted data. Essentially you have a race condition of multiple "writers" which is why we need a deterministic mechanism to update the cache, otherwise we cannot guarantee data integrity.
@undefobj ,
In the aws-appsync code there is also another link added when constructing the client:
new link_1.ComplexObjectLink(complexObjectsCredentials)
When I saw it, I guessed that this is needed for "complex object" support for AppSync where it also involves working with connected files on S3. Is that right?
I'm not using this functionality ATM, but would this functionality still be working with the new proposed solution with new Apollo with included auth and sub. links?
@undefobj , In the aws-appsync code there is also another link added when constructing the client:
new link_1.ComplexObjectLink(complexObjectsCredentials)
When I saw it, I guessed that this is needed for "complex object" support for AppSync where it also involves working with connected files on S3. Is that right? I'm not using this functionality ATM, but would this functionality still be working with the new proposed solution with new Apollo with included auth and sub. links?
Right now this is a separate link which is not published and still requires the older Apollo version. If there is high demand we can investigate publishing this as well, but I'd rather see if the core issue can be resolved.
Thanks to https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/448#issuecomment-541229659, it's now much cleaner with apollo-client. (without offline use cases)
Some notes:
a minor typo apsync
.
it shows an error: You are calling concat on a terminating link, which will have no effect
.
Modifying the code from the doc
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createSubscriptionHandshakeLink(url),
createHttpLink({ uri: url }),
]);
to
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createSubscriptionHandshakeLink(url),
]);
the error message is gone.
I think this is fine because of
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/50185bb0ff97191eae38e76d869552aad6ce09ad/packages/aws-appsync-subscription-link/src/index.ts#L33
and it has resultsFetcherLink = createHttpLink({ uri: url }))
as default.
I wish somebody could confirm that.
@dai-shi thanks for pointing this out
Using Typescript with this example. Here:
const auth = {
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials()
}
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
]);
auth
gets:
Type '{ type: AUTH_TYPE; credentials: () => Promise<ICredentials>; }' is not assignable to type 'AuthOptions'.
Type '{ type: AUTH_TYPE; credentials: () => Promise<ICredentials>; }' is not assignable to type 'AuthOptionsIAM'.
Types of property 'type' are incompatible.
Type 'AUTH_TYPE' is not assignable to type '"AWS_IAM"'.ts(2322)
index.d.ts(5, 5): The expected type comes from property 'auth' which is declared here on type '{ url: string; region: string; auth: AuthOptions; }'
Putting as const
after AWS_IAM
might solve it.
Hello everyone - I want to reply and assure you that we have not abandoned support of this project. Per my earlier comment, this is a complex process and to be completely transparent there are things in the Apollo library that are out of our control and there have also been breaking changes in Apollo versions which we're trying to account for, which is also why we pinned the version. We've been spending many hours trying to account for this but it's not simple to solve. After putting more thought into this we've got some initial solutions that I hope will unblock many of you. What we've done is packaged two of the "Links" which are part of the SDK - the Auth and Subscription Links - and published them to NPM. This will allow you to include them in the newer Apollo versions if you wish to use the newer features. This does not include the "Offline Link" for offline support which is more complex to solve and we're looking into for the future.
Installation of the links:
npm install aws-appsync-auth-link aws-appsync-subscription-link
With is I would then give the following recommendations for choosing your client strategy:
- If you do not have offline use cases, you can either use the Auth & Subscription Links above with the latest Apollo client or alternatively use the Amplify GraphQL client instead of Apollo:https://aws-amplify.github.io/docs/js/api#amplify-graphql-client
- If you do have offline use cases please use the current version as-is with the older Apollo version. We're still working on a future strategy for the Offline Link.
Please let us know if you have any trouble using these links or if there is a scenario which we haven't accounted for here.
@undefobj, does this solution works for SSR with IAM auth type? So far, I could not make it to work. I am setting it in the following way:
const auth = {
type: AUTH_TYPE.AWS_IAM as const,
credentials: () => Auth.currentCredentials()
}
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createHttpLink({ uri: url })
]);
and in the CloudWatch I see this error:
Request Headers: {x-amzn-ErrorType=[UnauthorizedException]}
Client-side rendering works as expected.
@sakhmedbayev I can't see why it wouldn't, nothing specific here is different with a static build. What I have seen in the past is that when people host apps and put an extra DNS layer in front of AppSync with IAM the signature will get stripped since it contains region information.
This solution doesn't seem to work with any other auth strategy other than API Key.
This solution doesn't seem to work with any other auth strategy other than API Key.
We've tested with React apps, however maybe this could be something with SSR. That's not something that has been tested.
I'm trying it in a react native expo app and didn't have any joy. Had to resort to the above Rehydrate hack, which is having other undesirable side effects.
Will keep going and report back with other findings.
I'm trying it in a react native expo app and didn't have any joy. Had to resort to the above Rehydrate hack, which is having other undesirable side effects.
Will keep going and report back with other findings.
Rehydrate is only used for Offline Support, which isn't supported in the custom links solution.
Got it. That worked. Have a new issue with sessions not persisting on reload, but I'll open a new issue with the amplify team
Worked great for my use case. In case anyone is interested in other authenticated methods, take a look at the definitions for the other auth methods in order to know the shape of the auth
param:
Does it mean that Cognito User Pools is not yet supported???
Hi guys, a few months ago I opened the issue https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/446 where I asked if it would be possible to evaluate an alternative to redux-offline to manage the offline link.
The libraries I created are compatible with react-relay 3.0 and I'm working on apollo-client 3.0 support (I'm also opening some PR in apollo-client for better integration https://github.com/apollographql/apollo-client/pull/5601).
Is it possible to have your feedback (even if negative)?
thanks
Does it mean that Cognito User Pools is not yet supported???
@sakhmedbayev Cognito User Pools is supported already
const auth = {
type: awsConfig.aws_appsync_authenticationType,
jwtToken: (async () => {
const currentSession = await Auth.currentSession();
return currentSession.getIdToken().getJwtToken()
})
};
Hello,
I got this working, but unfortunately for logged out users (using cognito pools), queries [that used to work] are perpetually stuck on 'loading', with no data for 'data' or 'error'. I have tried to rerun amplify auth update
, and ensured that I selected unauthed users to have access: Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) Yes
.
In the console:
"Possible Unhandled Promise Rejection (id: 1):
"No current user"
Auth.currentCredentials
returns what appears to be a valid CognitoIdentityCredentials
object, currentSession throws an error saying no current user.
I followed @undefobj 's writeup.
const url = awsconfig.aws_appsync_graphqlEndpoint;
const region = awsconfig.aws_appsync_region;
const auth = {
jwtToken: async () =>
(await Auth.currentSession()).getIdToken().getJwtToken(),
type: awsconfig.aws_appsync_authenticationType,
} as AuthOptions;
const httpLink = createHttpLink({ uri: url });
const link = ApolloLink.from([
createAuthLink({ auth, region, url }),
createSubscriptionHandshakeLink(url, httpLink),
]);
const client = new ApolloClient({
cache: new InMemoryCache(),
link,
});
Any suggestions?
446
@morrys I'm not sure about using something other than redux-offline. Looking at the alternatives, they don't address the core issue which is access to some of the cache update and notification properties in Apollo which is what we're struggling to solve in the newer versions. A couple team members are looking into this right now but it might take some efforts from the Apollo maintainers as well. We're evaluating a plan for how this could be offered as a custom link in 2020.
That being said there will always be fundamental struggles with doing offline when you're using a cache of query results which is what you have to do in the Apollo design. While we're going to continue to try and improve this for customers that want to use Apollo client we also have worked on an alternative solution with Amplify DataStore just released last week. If you're interested in offline apps it's our recommended solution to look first going forward: https://aws-amplify.github.io/docs/js/datastore
Hello,
I got this working, but unfortunately for logged out users (using cognito pools), queries [that used to work] are perpetually stuck on 'loading', with no data for 'data' or 'error'. I have tried to rerun
amplify auth update
, and ensured that I selected unauthed users to have access:Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) Yes
.In the console:
"Possible Unhandled Promise Rejection (id: 1): "No current user"
Auth.currentCredentials
returns what appears to be a validCognitoIdentityCredentials
object, currentSession throws an error saying no current user.I followed @undefobj 's writeup.
const url = awsconfig.aws_appsync_graphqlEndpoint; const region = awsconfig.aws_appsync_region; const auth = { jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(), type: awsconfig.aws_appsync_authenticationType, } as AuthOptions; const httpLink = createHttpLink({ uri: url }); const link = ApolloLink.from([ createAuthLink({ auth, region, url }), createSubscriptionHandshakeLink(url, httpLink), ]); const client = new ApolloClient({ cache: new InMemoryCache(), link, });
Any suggestions?
@VicFrolov this looks to be more of an issue with your auth state from User Pools, which could be several things. I'd suggest moving this specific question into the Amplify forums for troubleshooting with a team member.
hi @undefobj,
@morrys I'm not sure about using something other than redux-offline. Looking at the alternatives, they don't address the core issue which is access to some of the cache update and notification properties in Apollo which is what we're struggling to solve in the newer versions. A couple team members are looking into this right now
What I'd like to have is a discussion with you about the alternative I propose. For those who know Apollo are two libraries (wora/apollo-cache & wora/apollo-offline) that do not require a huge effort.
but it might take some efforts from the Apollo maintainers as well. We're evaluating a plan for how this could be offered as a custom link in 2020.
I agree, for this reason I propose PR to Apollo in order to improve integration.
If you're interested in offline apps it's our recommended solution to look first going forward: https://aws-amplify.github.io/docs/js/datastore
I'll try it :)
Meanwhile, thanks for the answer and if I have not convinced you to evaluate my approach (also used to manage relay react-relay-appsync), you can also close the issue https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/446
@morrys I think we can leave it open for now. It's not that alternatives to redux offline aren't worth evaluating, it's more that isn't the current blocker that we're trying to resolve. Even if there was a different offline link implementation we'd still have the same core Apollo cache situation to overcome.
@undefobj, my approach to managing the offline is to extend the apollo client without using offline link.
The biggest problem in managing the apollo cache, in offline context, is caused by the use of server-side generated IDs. This problem is easily overcome by generating client-side IDs or avoiding "mutations" of new items. (I think this is one of your problems)
I have overcome other cache iteration problems with wora/apollo-cache (more details are in this PR https://github.com/apollographql/apollo-client/pull/5601)
Another PR that will surely be interesting for you too is this: https://github.com/apollographql/apollo-client/pull/5649
Other problems can be caused during the parallel execution of mutations in the queue, default behavior of redux-offline, while In wora/apollo-offline the default execution is serial and is easily configurable for each mutation through its functions of callback
Server generated IDs aren't an issue we have a request mapping for that which results in a deterministic update of the query references. The problems are more related to global notification of cache updates using the QueryManager as well as consistency issues reconciled with local resolvers.
I have not detected these problems .. do you have any issues with more details or some example project where the error is noticeable?
@undefobj, are there good examples of how to use DataStore with either Next.js or Gatsby? I would greatly appreciate it if you can point me somewhere. Thanks!
I have fixed the same error by downgrading the apollo to 2.5.8 .. But i still get some warninig:
Possible Unhandled Promise Rejection (id: 0): TypeError: undefined is not an object (evaluating 'netinfo_1.default.isConnected.fetch')
"dependencies": { "@react-native-community/async-storage": "^1.7.1", "@react-native-community/netinfo": "^5.0.0", "amazon-cognito-identity-js": "^3.2.0", "apollo-cache-hermes": "^0.8.10", "aws-amplify": "^2.2.0", "aws-amplify-react-native": "^2.2.3", "aws-appsync": "^3.0.2", "aws-appsync-auth-link": "^2.0.1", "aws-appsync-react": "^3.0.2", "aws-appsync-subscription-link": "^2.0.1", "react": "16.9.0", "react-apollo": "^2.5.8", "react-native": "0.61.5", "reactotron-react-native": "^4.0.2" },
[FIXED] - Downgrade -> "@react-native-community/netinfo": "^4.0.0",
Any progress on this issue? Also, publishing the complexObjectLink, compatible with the new version would be nice.
I have the same issue
I ran into this issue trying to follow the basic instructions in the README which don't mention incompatibility with react-apollo 3.x. Perhaps they should clearly state that AppSync requires you to use an old Apollo version.
Do you want to request a feature or report a bug? Bug
What is the current behavior? Installing react-apollo 3.0 makes the Rehydrated component stop working. Going back to react-apollo 2.5.8 makes it work again
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Error message recieved: The context
client
is marked as required inRehydrated
, but its value isundefined
.What is the expected behavior? Rehydrated should have a client to be able to rehydrate