Comments (19)
Took me a while to pull these together...
- the
query
text expected by the server
Using the requestDidStart
and didEncounterErrors
plugins on the server, the client sends this for matches
:
api_1-1 | [07/02/2024 16:01:48.946] [LOG] errors_here: provided sha does not match query
api_1-1 | [07/02/2024 16:01:48.970] [LOG] {
'query getMatches($userId: Int!) {\n' +
' matches(user_id: $userId) {\n' +
' matchesId\n' +
' swipedByUser {\n' +
' user_id\n' +
' name\n' +
' image_url\n' +
' description\n' +
' city\n' +
' country\n' +
' __typename\n' +
' }\n' +
' swipedUser {\n' +
' user_id\n' +
' __typename\n' +
' }\n' +
' createdAt\n' +
' operationType\n' +
' __typename\n' +
' }\n' +
'}'
}
- the hash for that query expected on the server
api_1-1 | http: {
api_1-1 | method: 'GET',
api_1-1 | headers: [HeaderMap [Map]],
_api_1-1 | search: '?operationName=getMatches&variables=%7B%22userId%22%3A002%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2245c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665%22%7D%7D',_
api_1-1 | body: {}
api_1-1 | }
- the
query
text the client would send out (console.log it when calculating the hash)
The client doesnt actually show the query text, when you breakdown the query
param, this is what you get, this is even with ApolloLink
logging outgoing requests:
INFO request name getMatches
INFO request query [{"directives": [], "kind": "OperationDefinition", "name": {"kind": "Name", "value": "getMatches"}, "operation": "query", "selectionSet": {"kind": "SelectionSet", "selections": [Array]}, "variableDefinitions": [[Object]]}]
- the hash that the client is sending out
LOG {"query_name": "getMatches", "sha256Hash": "45c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665"}
I noticed the hash expected with getMatches
2245c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665 on the server is different from what client is sending i.e 45c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665. The question is, how does it attach a different hash value and how do I obtain this if the client is expecting a hash function
to be provided?
from apollo-client.
@bwoodlt Great to hear that you found it! I'm sorry I couldn't really help more here!
from apollo-client.
I think your problem here is client.query
as opposed to client.watchQuery
- client.query
will only ever wait for the first part of the data to stream in and not receive any updates beyond that - that means you'll never be able to access the deferred parts of the query.
Could it be that your error here is caused by userland code that tries to access node.subscriptions.length
?
PS: usually you should not copy data from Apollo Client over into Redux - Apollo Client is already a fully fledged cache, and copying that data into Redux would mean that you write code for something that has already been handled for you by the library. I'd recommend to use Apollo Client for your GraphQL data and Redux for the application state beyond that.
from apollo-client.
Thanks @phryneas makes sense!
from apollo-client.
@bwoodlt I also want to call your attention to this section in the docs on the polyfills needed to consume multipart responses in React Native, in case you haven't come across it yet.
from apollo-client.
@alessbell yeah I've got that setup in place. I've now been able to get the data via watchQuery
.. Though we wouldnt be able to use this yet as it may cause additional load on the server, will also mean some refactoring so will revisit another time. thanks both!
I'm happy to create another ticket, but wanted to highlight another issue i came across with persisted queries having followed instructions here https://www.apollographql.com/docs/apollo-server/performance/apq/
I have this code:
const generateSHA256Hash = query => {
return sha256(query).toString();
};
const httpLink = new HttpLink({
uri: API_LINK_URL,
fetch: customFetch
});
const persistedQueryLink = createPersistedQueryLink({
useGETForHashedQueries: true,
generateHash: generateSHA256Hash
}).concat(httpLink);
And inside the client
initialization:
const client = new ApolloClient({
link: from([
onError(error => {
let { networkError = {}, graphQLErrors = [] } = error || {};
try {
if (graphQLErrors) {
Array.isArray(graphQLErrors) &&
graphQLErrors.forEach(({ extensions, message, locations, path }) => {
console.log(
`[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}, Type: ${extensions.code}`
);
console.log(extensions?.exception);
});
}
if (networkError && typeof networkError === 'object' && Object.keys(networkError).length) {
console.log({ networkError });
}
} catch (e) {
networkError = error.bodyText;
}
}),
split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
wsLink,
authLink.concat(persistedQueryLink) // using the persisted query link here
)
]),
...other_props,
})
// this is returning `provided sha does not match query,` error because there is a check at line no 56 in node_modules/apollo-server-core/dist/requestPipeline.js.
const computedQueryHash = computeQueryHash(query);
if (queryHash !== computedQueryHash) {
return await sendErrorResponse(new graphql_1.GraphQLError(‘provided sha does not match query’));
}
Any thoughts on what might be wrong?
from apollo-client.
Though we wouldnt be able to use this yet as it may cause additional load on the server, will also mean some refactoring so will revisit another time. thanks both!
There might be a bit of confusion about what watchQuery
does here: client.watchQuery
won't cause any more strain on the server than client.query
.
From the server perspective, both are exactly the same - the difference is on the client. query
will only give you the first sufficient part of a result, while watchQuery
will give you all results of a request, and future cache updates.
this is returning
provided sha does not match query,
error because there is a check at line no 56 in node_modules/apollo-server-core/dist/requestPipeline.js.
Are you 100% sure it is the same query? Apollo Client will do things like add __typename
to the outgoing query, and that will also be reflected in your hash.
from apollo-client.
There might be a bit of confusion about what
watchQuery
does here:client.watchQuery
won't cause any more strain on the server thanclient.query
. From the server perspective, both are exactly the same - the difference is on the client.query
will only give you the first sufficient part of a result, whilewatchQuery
will give you all results of a request, and future cache updates.
Right, that makes sense. Assumption around watchQuery
was this uses a polling
mechanism internally, and that will impact. So will test abit more later today. Thanks for clarifying!
Are you 100% sure it is the same query? Apollo Client will do things like add
__typename
to the outgoing query, and that will also be reflected in your hash.
This is the first time the query will be hashed (sent to the server with the hash value), the hash generated is something like so 4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e
and i wonder whether the algorithm that creates the expected hash in ApolloServer is different from what we use on the client side.
We're using crypto-js/sha256
to generate hash, ApolloServer is computing its hash using the crypto
module like so:
function computeQueryHash(query: string) {
return createHash('sha256').update(query).digest('hex');
}
and checking like so:
// The provided hash must exactly match the SHA-256 hash of
// the query string. This prevents hash hijacking, where a
// new and potentially malicious query is associated with
// an existing hash.
if (queryHash !== computedQueryHash) {
return await sendErrorResponse([
new GraphQLError('provided sha does not match query', {
extensions: { http: newHTTPGraphQLHead(400) },
}),
]);
}
So if its expecting the provided hash by the client to be the same as the one computeQueryHash
generate, the algo might be different?
Sorry for lots of code, i'll rather understand implementation with confidence than just follow any random guide online :)
from apollo-client.
My point is not that the hash algorithm is different, but that the query that is being hashed is different.
Apollo Client will change your query before sending it to the server/hashing it:
const GET_USERS = gql`
query users($first: Int, $last: int, $id: int) { // im using relay connection args in BE for pagination
users(first: $first, last: $last, id: $id) {
+ __typename
pageInfo {
+ __typename
endCursor
}
edges {
+ __typename
node {
+ __typename
name
image_url
... @defer {
subscriptions {
+ __typename
status
}
}
}
}
}
}
`
So a hash of that would be different from a hash of that without these changes.
from apollo-client.
My point is not that the hash algorithm is different, but that the query that is being hashed is different.
The query remains the same. Given your example above, the same users(id: !int) { ... }
is fetched at all times. Nothing changes on that front. Although, whilst debugging the logic in persisted-queries.cjs.native
, I noticed the generated query and hashes are the same for all queries which doesnt look normal to me...
LOG {"operation": undefined, "query_name": "UserPostsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": undefined, "query_name": "PostCommentsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": "query", "query_name": "notifications", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": undefined, "query_name": "PostCommentsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": undefined, "query_name": "UserPostsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": undefined, "query_name": "PostLikesFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": "query", "query_name": "getPartners", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
LOG {"operation": "query", "query_name": "users", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
Do you have a sample project you can point me to where I could see this in action?
from apollo-client.
Curiously that exact hash turns up on google in an example where a hash function is used incorrectly: https://stackoverflow.com/questions/59990295/why-is-the-hash-of-this-file-staying-the-same-when-i-change-its-data
Are these hash values calculated on the server or in the browser?
from apollo-client.
Are these hash values calculated on the server or in the browser?
we generate them on the client.
So by modifying the generateHash
fn to stringify the query sha256(JSON.stringify(query)).toString();
, this returns unique hash
values. Although, still getting provided sha does not match query
error
LOG {"operation": "query", "query_name": "getMatches", "sha256Hash": "17389ea0cc05abb508005fa9d24bda3b0c71a266a299c24e867bb3c491337aea"}
LOG {"operation": "query", "query_name": "users", "sha256Hash": "7a38186d4be8632c7432aa8cf3389747fc50532e62bb5ea079ce00d98263a0d7"}
from apollo-client.
I still think that your server might not account for modification of these queries before they go out.
Could you please provide these for one query:
- the
query
text expected by the server - the hash for that query expected on the server
- the
query
text the client would send out (console.log it when calculating the hash) - the hash that the client is sending out
from apollo-client.
INFO request name getMatches
INFO request query [{"directives": [], "kind": "OperationDefinition", "name": {"kind": "Name", "value": "getMatches"}, "operation": "query", "selectionSet": {"kind": "SelectionSet", "selections": [Array]}, "variableDefinitions": [[Object]]}]
Could you send that through print
(exported from @apollo/client/utilities
) before logging it out?
from apollo-client.
2245c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665
didnt know about print
until now. Nice one!
// client request
LOG {"query": "query getMatches($userId: Int!) {
matches(user_id: $userId) {
matchesId
swipedByUser {
user_id
name
image_url
description
city
country
__typename
}
swipedUser {
user_id
__typename
}
createdAt
operationType
__typename
}
}"}
from apollo-client.
@phryneas have you had a moment to take a look at this further? Would you know why there are discrepancies between hash values received on the server vs what was sent from the client?
from apollo-client.
We're really not doing anything different from sha256(print(query))
so at this point I honestly don't know.
You might have a weird hash function in one of both environments - I'd recommend hashing known strings in both environments and comparing them to an online hash calculator.
from apollo-client.
I've manage to sort out this issue. For anyone coming across this perhaps from google, just ensure your client doesnt stringfy
the query before hashing the value. Ensure you are hashing just like @phryneas stated above.
Thanks all!
from apollo-client.
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.
from apollo-client.
Related Issues (20)
- Allow async onCompleted & onError callbacks HOT 11
- improperly escaped error arguments for go.apollo.dev links HOT 4
- Java ApolloClient SocketTimeoutException, how to fix HOT 2
- Tracking issue: `gql.tada` HOT 1
- Apollo client initiated requests since apollo client v3.9.2 in Nextjs sometimes render as undefined data HOT 9
- useFragment fails to get data from cache for an Interface, even when possibleTypes is setup correctly HOT 5
- Better types MockedResponse.newData HOT 2
- graphql union type extraction fragment doesn't get mapping field values HOT 4
- Question about using useTransition with useSuspenseQuery's refetch and fetchMore after SSR HOT 2
- Error while build the angular app with SSR HOT 1
- `ObservableQuery` `setVariables`/`reobserve` with new variables reports partial data unexpectedly
- Invariant Violation: An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#%7B%22version%22%3A%223.9.4%22%2C%22message%22%3A49%2C%22args%22%3A%5B%5D%7D HOT 8
- offsetLimitPagination helper is not using cache in some cases HOT 2
- Better documentation on `queryDeduplication` HOT 1
- useFragment invalid return value in the case of null or undefined id in the from argument HOT 1
- Refetch with New Variables Overrides Other Existing Refetches HOT 3
- Increase the timeout for a request. HOT 6
- Apollo Client `MockedProvider` unable to mock alias fields HOT 6
- `client.query`, `fetchMore`, and others suffer from unhandled promise rejections
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from apollo-client.