From dd59de3de0edb50201f7231c3120ba1cc66dcc5f Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 27 Oct 2021 22:41:32 +0530 Subject: [PATCH] feat: add subscription support for useGQLQuery --- .../helpers/backend/GQLClient.ts | 42 ++++++++++++++++++- .../helpers/backend/caching/updates.ts | 38 +++++++++++++++++ packages/hoppscotch-app/package.json | 1 + pnpm-lock.yaml | 37 +++------------- 4 files changed, 85 insertions(+), 33 deletions(-) diff --git a/packages/hoppscotch-app/helpers/backend/GQLClient.ts b/packages/hoppscotch-app/helpers/backend/GQLClient.ts index 79c6eeae0..fedad795c 100644 --- a/packages/hoppscotch-app/helpers/backend/GQLClient.ts +++ b/packages/hoppscotch-app/helpers/backend/GQLClient.ts @@ -18,11 +18,13 @@ import { makeOperation, GraphQLRequest, createRequest, + subscriptionExchange, } from "@urql/core" import { authExchange } from "@urql/exchange-auth" import { offlineExchange } from "@urql/exchange-graphcache" import { makeDefaultStorage } from "@urql/exchange-graphcache/default-storage" import { devtoolsExchange } from "@urql/devtools" +import { SubscriptionClient } from "subscriptions-transport-ws" import * as E from "fp-ts/Either" import * as TE from "fp-ts/TaskEither" import { pipe, constVoid } from "fp-ts/function" @@ -33,13 +35,14 @@ import { updatesDef } from "./caching/updates" import { resolversDef } from "./caching/resolvers" import schema from "./backend-schema.json" import { + authIdToken$, getAuthIDToken, probableUser$, waitProbableLoginToConfirm, } from "~/helpers/fb/auth" const BACKEND_GQL_URL = - process.env.CONTEXT === "production" + process.env.context === "production" ? "https://api.hoppscotch.io/graphql" : "https://api.hoppscotch.io/graphql" @@ -48,6 +51,18 @@ const storage = makeDefaultStorage({ maxAge: 7, }) +const subscriptionClient = new SubscriptionClient( + process.env.context === "production" + ? "wss://api.hoppscotch.io/graphql" + : "wss://api.hoppscotch.io/graphql", + { + reconnect: true, + connectionParams: () => ({ + authorization: `Bearer ${authIdToken$.value}`, + }), + } +) + export const client = createClient({ url: BACKEND_GQL_URL, exchanges: [ @@ -97,6 +112,9 @@ export const client = createClient({ }, }), fetchExchange, + subscriptionExchange({ + forwardSubscription: (operation) => subscriptionClient.request(operation), + }), ], }) @@ -106,6 +124,7 @@ type UseQueryOptions = { query: TypedDocumentNode variables?: MaybeRef + updateSubs?: MaybeRef[]> defer?: boolean } @@ -133,6 +152,8 @@ export const useGQLQuery = ( const isStale: Ref = ref(true) const data: Ref, DocType>> = ref() as any + if (!args.updateSubs) set(args, "updateSubs", []) + const isPaused: Ref = ref(args.defer ?? false) const request: Ref> = ref( @@ -178,7 +199,22 @@ export const useGQLQuery = ( loading.value = true isStale.value = false - onInvalidate( + const invalidateStops = args.updateSubs!.map((sub) => { + console.log("create sub") + + return wonkaPipe( + client.executeSubscription(sub), + onEnd(() => { + if (source.value) execute() + }), + subscribe(() => { + console.log("invalidate!") + return execute() + }) + ).unsubscribe + }) + + invalidateStops.push( wonkaPipe( source.value, onEnd(() => { @@ -220,6 +256,8 @@ export const useGQLQuery = ( }) ).unsubscribe ) + + onInvalidate(() => invalidateStops.forEach((unsub) => unsub())) } }) diff --git a/packages/hoppscotch-app/helpers/backend/caching/updates.ts b/packages/hoppscotch-app/helpers/backend/caching/updates.ts index 2dad18248..d13e9c1fe 100644 --- a/packages/hoppscotch-app/helpers/backend/caching/updates.ts +++ b/packages/hoppscotch-app/helpers/backend/caching/updates.ts @@ -1,6 +1,44 @@ import { GraphCacheUpdaters, MyTeamsDocument } from "../graphql" export const updatesDef: GraphCacheUpdaters = { + Subscription: { + teamMemberAdded: (_r, { teamID }, cache, _info) => { + debugger + cache.invalidate( + { + __typename: "Team", + id: teamID, + }, + "teamMembers" + ) + }, + teamMemberUpdated: (_r, { teamID }, cache, _info) => { + cache.invalidate( + { + __typename: "Team", + id: teamID, + }, + "teamMembers" + ) + + cache.invalidate( + { + __typename: "Team", + id: teamID, + }, + "myRole" + ) + }, + teamMemberRemoved: (_r, { teamID }, cache, _info) => { + cache.invalidate( + { + __typename: "Team", + id: teamID, + }, + "teamMembers" + ) + }, + }, Mutation: { deleteTeam: (_r, { teamID }, cache, _info) => { cache.updateQuery( diff --git a/packages/hoppscotch-app/package.json b/packages/hoppscotch-app/package.json index 2bcc217f2..957d03645 100644 --- a/packages/hoppscotch-app/package.json +++ b/packages/hoppscotch-app/package.json @@ -72,6 +72,7 @@ "socket.io-client-v4": "npm:socket.io-client@^4.3.2", "socketio-wildcard": "^2.0.0", "splitpanes": "^2.3.8", + "subscriptions-transport-ws": "^0.9.19", "tern": "^0.24.3", "uuid": "8.3.2", "vue-apollo": "^3.0.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9995c3f18..134a9c4e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,7 @@ importers: stylelint: ^13.13.1 stylelint-config-prettier: ^9.0.3 stylelint-config-standard: ^22.0.0 + subscriptions-transport-ws: ^0.9.19 tern: ^0.24.3 ts-jest: ^27.0.7 typescript: ^4.4.4 @@ -124,7 +125,7 @@ importers: worker-loader: ^3.0.8 yargs-parser: ^20.2.9 dependencies: - '@apollo/client': 3.4.16_graphql@15.7.2 + '@apollo/client': 3.4.16_e655a905d8577dd77bbe79c6fb42490b '@hoppscotch/js-sandbox': link:../hoppscotch-js-sandbox '@nuxtjs/axios': 5.13.6 '@nuxtjs/composition-api': 0.29.3_nuxt@2.15.8 @@ -163,6 +164,7 @@ importers: socket.io-client-v4: /socket.io-client/4.3.2 socketio-wildcard: 2.0.0 splitpanes: 2.3.8 + subscriptions-transport-ws: 0.9.19_graphql@15.7.2 tern: 0.24.3 uuid: 8.3.2 vue-apollo: 3.0.8_graphql-tag@2.12.5 @@ -310,33 +312,6 @@ packages: zen-observable-ts: 1.1.0 dev: false - /@apollo/client/3.4.16_graphql@15.7.2: - resolution: {integrity: sha512-iF4zEYwvebkri0BZQyv8zfavPfVEafsK0wkOofa6eC2yZu50J18uTutKtC174rjHZ2eyxZ8tV7NvAPKRT+OtZw==} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 - react: ^16.8.0 || ^17.0.0 - subscriptions-transport-ws: ^0.9.0 - peerDependenciesMeta: - react: - optional: true - subscriptions-transport-ws: - optional: true - dependencies: - '@graphql-typed-document-node/core': 3.1.0_graphql@15.7.2 - '@wry/context': 0.6.1 - '@wry/equality': 0.5.2 - '@wry/trie': 0.3.1 - graphql: 15.7.2 - graphql-tag: 2.12.5_graphql@15.7.2 - hoist-non-react-statics: 3.3.2 - optimism: 0.16.1 - prop-types: 15.7.2 - symbol-observable: 4.0.0 - ts-invariant: 0.9.3 - tslib: 2.3.1 - zen-observable-ts: 1.1.0 - dev: false - /@apollo/federation/0.27.0_graphql@15.7.2: resolution: {integrity: sha512-hMeRN9IPsIn+5J5SmWof0ODbvRjRj8mBNqbsm9Zjkqjbw6RTlcx90taMk7cYhcd/E+uTyLQt5cOSRVBx53cxbQ==} engines: {node: '>=12.13.0 <17.0'} @@ -3179,7 +3154,7 @@ packages: form-data: 4.0.0 graphql: 15.7.2 graphql-sse: 1.0.4_graphql@15.7.2 - graphql-ws: 5.5.0_graphql@15.7.2 + graphql-ws: 5.5.3_graphql@15.7.2 is-promise: 4.0.0 isomorphic-ws: 4.0.1_ws@8.2.2 lodash: 4.17.21 @@ -10686,8 +10661,8 @@ packages: graphql: 15.7.2 dev: false - /graphql-ws/5.5.0_graphql@15.7.2: - resolution: {integrity: sha512-WQepPMGQQoqS2VsrI2I3RMLCVz3CW4/6ZqGV6ABDOwH4R62DzjxwMlwZbj6vhSI/7IM3/C911yITwgs77iO/hw==} + /graphql-ws/5.5.3_graphql@15.7.2: + resolution: {integrity: sha512-Okp3gE3vq9OoeqsYVbmzKvPcvlinKNXrfVajH7D3ul1UdCg2+K2zVYbWKmqxehkAZ+GKVfngK5fzyXSsfpe+pA==} engines: {node: '>=10'} peerDependencies: graphql: '>=0.11 <=16'