Repository: FormidableLabs/urql
Pull request #870 (2020-06-17 21:30:14)

(persisted) - Add generateHash option to the persistedFetchExchange
<!--
  Thanks for opening a pull request! We appreciate your dedication and help!
  Before submitting your pull request, please make sure to read our CONTRIBUTING guide.

  The best contribution is always a PR! However, if you're starting to work on a large
  change, it's best to make sure to open an issue first.

  If this PR is already related to an issue, please reference it like so:
  Resolves #123
-->

## Summary

<!-- What's the motivation of this change? What does it solve? -->
This PR adds support for a custom hash function in the persisted query exchange. This is useful for non-browser environments like react-native, where you might want to use a different crypto API ([like Expo's](https://docs.expo.io/versions/v37.0.0/sdk/crypto/), for instance).

Usage:

```ts
import sha256 from "my-library";

const client = createClient({
  url: 'http://localhost:1234/graphql',
  exchanges: [
    dedupExchange,
    cacheExchange,
    persistedFetchExchange({ generateHash: sha256 }),
    fetchExchange
  ],
});

```

## Set of changes

<!--
  Roughly list the changes you've made and which packages are affected.
  Leave some notes on what may be noteworthy files you've changed.
  And lastly, please let us know if you think this is a breaking change.
-->

Adds an argument to the `persistedFetchExchange` factory. No breaking changes.


changeset-bot[bot]: ###  💥  No Changeset

Latest commit: 2eeb348afb43359a464f2f245c4e1e91c5a24d23

Merging this PR will not cause any packages to be released. If these changes should not cause updates to packages in this repo, this is fine 🙂

**If these changes should be published to npm, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/lorenries/urql/new/custom-hash?filename=.changeset/curly-roses-taste.md&value=---%0A%22%40urql%2Fexchange-persisted-fetch%22%3A%20patch%0A---%0A%0AAdds%20support%20for%20a%20custom%20hash%20function%20in%20%40urql%2Fexchange-persisted-fetch%0A)



Patch:
diff --git a/.changeset/green-lies-help.md b/.changeset/green-lies-help.md
new file mode 100644
index 00000000..9a63d3ea
--- /dev/null
+++ b/.changeset/green-lies-help.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-persisted-fetch': minor
+---
+
+Adds support for custom hash functions by adding a `generateHash` option to the exchange.
diff --git a/exchanges/persisted-fetch/README.md b/exchanges/persisted-fetch/README.md
index 1a5ec6b8..1c524f88 100644
--- a/exchanges/persisted-fetch/README.md
+++ b/exchanges/persisted-fetch/README.md
@@ -25,11 +25,18 @@ const client = createClient({
   exchanges: [
     dedupExchange,
     cacheExchange,
-    persistedFetchExchange,
+    persistedFetchExchange({
+      /* optional config */
+    }),
     fetchExchange
   ],
 });
 ```
 
+The `persistedQueryExchange` supports two configuration options:
+
+- `preferGetForPersistedQueries`: Use `GET` for fetches with persisted queries
+- `generateHash`: A function that takes a GraphQL query and returns the hashed result. This defaults to the `window.crypto` API in the browser and the `crypto` module in node.
+
 The `persistedFetchExchange` only handles queries, so for mutations we keep the
 `fetchExchange` around alongside of it.
diff --git a/exchanges/persisted-fetch/src/persistedFetchExchange.ts b/exchanges/persisted-fetch/src/persistedFetchExchange.ts
index c58a6a78..2191e088 100644
--- a/exchanges/persisted-fetch/src/persistedFetchExchange.ts
+++ b/exchanges/persisted-fetch/src/persistedFetchExchange.ts
@@ -32,6 +32,7 @@ import { hash } from './sha256';
 
 interface PersistedFetchExchangeOptions {
   preferGetForPersistedQueries?: boolean;
+  generateHash?: (query: string) => Promise<string>;
 }
 
 export const persistedFetchExchange = (
@@ -39,6 +40,7 @@ export const persistedFetchExchange = (
 ): Exchange => ({ forward, dispatchDebug }) => {
   if (!options) options = {};
 
+  const hashFn = options.generateHash || hash;
   let supportsPersistedQueries = true;
 
   return ops$ => {
@@ -66,7 +68,7 @@ export const persistedFetchExchange = (
 
         return pipe(
           // Hash the given GraphQL query
-          fromPromise(hash(query)),
+          fromPromise(hashFn(query)),
           mergeMap(sha256Hash => {
             // Attach SHA256 hash and remove query from body
             body.query = undefined;


Test patch:
diff --git a/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts b/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts
index 79be2811..01ae9c0a 100644
--- a/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts
+++ b/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts
@@ -193,3 +193,36 @@ it('correctly generates an SHA256 hash', async () => {
     },
   });
 });
+
+it('supports a custom hash function', async () => {
+  const expected = {
+    data: {
+      test: true,
+    },
+  };
+
+  fetch.mockResolvedValueOnce({
+    json: () => expected,
+  });
+
+  const hashFn = () => Promise.resolve('hello');
+
+  await pipe(
+    fromValue(queryOperation),
+    persistedFetchExchange({ generateHash: hashFn })(exchangeArgs),
+    toPromise
+  );
+
+  expect(fetch).toHaveBeenCalledTimes(1);
+
+  const body = JSON.parse(fetch.mock.calls[0][1].body);
+
+  expect(body).toMatchObject({
+    extensions: {
+      persistedQuery: {
+        version: 1,
+        sha256Hash: 'hello',
+      },
+    },
+  });
+});