Comments (20)
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.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.
from apollo-client.
Related Issues (20)
- Initializing ApolloClient before DataDog RUM causes GraphQL calls to not have the correct headers HOT 5
- Skipped Query returns outdated data after cache clearing HOT 7
- Merge function at the type level not working (+ suggested fix) HOT 13
- Unexpected Data Fetching with `useBackgroundQuery`, StrictMode, `fetchPolicy`: `network-only` (or `no-cache`), and state changes HOT 5
- When use the Apollo Client server side rendering, How to share the cache in cluster server (k8s)? ( did we have file cache? or db cache? ) HOT 3
- Feedback for schema-driven testing utilities HOT 1
- Add `subscribeToMore` functionality to `useQueryRefHandlers`, `useBackgroundQuery`
- [Docs] Anchor links seem to be stripped HOT 4
- Incomprehensible type mismatch with query generics HOT 3
- Changes to `context` cause refetches in `useQuery` HOT 11
- Add an option in ApolloClient constructor to return a deep copy of query results (or a way to globally transform results after the caching step) HOT 2
- Potentially unintentionally large AoE breaking changing to typescript `MockedResponse` in type 3.9 HOT 4
- clearStore causes a crash when there is an active query HOT 3
- Getting A on the result.data HOT 2
- BatchHTTPLink is not monitoring friendly HOT 5
- Support extensions in useSubscription HOT 1
- React functionality 'useContext' is not available in this environment [Nextjs app] HOT 3
- Subscription doesn't deduplicate HOT 3
- Types `cache.modify` and `writeFragment` lead to TypeScript errors for documented Apollo examples when specifying generic parameter HOT 1
- SOLVED : TypeError: Cannot return null for non-nullable field Subscription: Next.js frontend + FastApi backend HOT 2
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.