From 4d19b9249ba99f281ccae543a43df7b1f1ca4a36 Mon Sep 17 00:00:00 2001 From: Akash K <57758277+amk-dev@users.noreply.github.com> Date: Tue, 11 Oct 2022 14:06:22 +0530 Subject: [PATCH] fix: fix auth/graphql errors causing errors on logout (#2772) --- .../src/helpers/backend/GQLClient.ts | 180 ++++++++++-------- .../src/helpers/teams/TeamListAdapter.ts | 4 + 2 files changed, 106 insertions(+), 78 deletions(-) diff --git a/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts b/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts index a703d618a..7605b5651 100644 --- a/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts +++ b/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts @@ -54,88 +54,112 @@ export type GQLClientErrorEvent = */ export const gqlClientError$ = new Subject() -const subscriptionClient = new SubscriptionClient(BACKEND_WS_URL, { - reconnect: true, - connectionParams: () => { - return { - authorization: `Bearer ${authIdToken$.value}`, - } - }, - connectionCallback(error) { - if (error?.length > 0) { - gqlClientError$.next({ - type: "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT", - errors: error, - }) - } - }, -}) - -authIdToken$.subscribe(() => { - subscriptionClient.client?.close() -}) - -const createHoppClient = () => - createClient({ - url: BACKEND_GQL_URL, - exchanges: [ - devtoolsExchange, - dedupExchange, - authExchange({ - addAuthToOperation({ authState, operation }) { - if (!authState || !authState.authToken) { - return operation - } - - const fetchOptions = - typeof operation.context.fetchOptions === "function" - ? operation.context.fetchOptions() - : operation.context.fetchOptions || {} - - return makeOperation(operation.kind, operation, { - ...operation.context, - fetchOptions: { - ...fetchOptions, - headers: { - ...fetchOptions.headers, - Authorization: `Bearer ${authState.authToken}`, - }, - }, - }) - }, - willAuthError({ authState }) { - return !authState || !authState.authToken - }, - getAuth: async () => { - if (!probableUser$.value) return { authToken: null } - - await waitProbableLoginToConfirm() - - return { - authToken: getAuthIDToken(), - } - }, - }), - fetchExchange, - subscriptionExchange({ - forwardSubscription: (operation) => - subscriptionClient.request(operation), - }), - errorExchange({ - onError(error, op) { - gqlClientError$.next({ - type: "CLIENT_REPORTED_ERROR", - error, - op, - }) - }, - }), - ], +const createSubscriptionClient = () => { + return new SubscriptionClient(BACKEND_WS_URL, { + reconnect: true, + connectionParams: () => { + return { + authorization: `Bearer ${authIdToken$.value}`, + } + }, + connectionCallback(error) { + if (error?.length > 0) { + gqlClientError$.next({ + type: "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT", + errors: error, + }) + } + }, }) +} +const createHoppClient = () => { + const exchanges = [ + devtoolsExchange, + dedupExchange, + authExchange({ + addAuthToOperation({ authState, operation }) { + if (!authState || !authState.authToken) { + return operation + } + + const fetchOptions = + typeof operation.context.fetchOptions === "function" + ? operation.context.fetchOptions() + : operation.context.fetchOptions || {} + + return makeOperation(operation.kind, operation, { + ...operation.context, + fetchOptions: { + ...fetchOptions, + headers: { + ...fetchOptions.headers, + Authorization: `Bearer ${authState.authToken}`, + }, + }, + }) + }, + willAuthError({ authState }) { + return !authState || !authState.authToken + }, + getAuth: async () => { + if (!probableUser$.value) return { authToken: null } + + await waitProbableLoginToConfirm() + + return { + authToken: getAuthIDToken(), + } + }, + }), + fetchExchange, + errorExchange({ + onError(error, op) { + gqlClientError$.next({ + type: "CLIENT_REPORTED_ERROR", + error, + op, + }) + }, + }), + ] + + if (subscriptionClient) { + exchanges.push( + subscriptionExchange({ + forwardSubscription: (operation) => { + return subscriptionClient!.request(operation) + }, + }) + ) + } + + return createClient({ + url: BACKEND_GQL_URL, + exchanges, + }) +} + +let subscriptionClient: SubscriptionClient | null export const client = ref(createHoppClient()) -authIdToken$.subscribe(() => { +authIdToken$.subscribe((idToken) => { + // triggering reconnect by closing the websocket client + if (idToken && subscriptionClient) { + subscriptionClient?.client?.close() + } + + // creating new subscription + if (idToken && !subscriptionClient) { + subscriptionClient = createSubscriptionClient() + } + + // closing existing subscription client. + if (!idToken && subscriptionClient) { + subscriptionClient.close() + subscriptionClient = null + } + client.value = createHoppClient() }) diff --git a/packages/hoppscotch-app/src/helpers/teams/TeamListAdapter.ts b/packages/hoppscotch-app/src/helpers/teams/TeamListAdapter.ts index 1619862c2..16db0acb1 100644 --- a/packages/hoppscotch-app/src/helpers/teams/TeamListAdapter.ts +++ b/packages/hoppscotch-app/src/helpers/teams/TeamListAdapter.ts @@ -2,6 +2,7 @@ import * as E from "fp-ts/Either" import { BehaviorSubject } from "rxjs" import { GQLError, runGQLQuery } from "../backend/GQLClient" import { GetMyTeamsDocument, GetMyTeamsQuery } from "../backend/graphql" +import { authIdToken$ } from "~/helpers/fb/auth" const BACKEND_PAGE_SIZE = 10 const POLL_DURATION = 10000 @@ -46,6 +47,9 @@ export default class TeamListAdapter { } async fetchList() { + // if the authIdToken is not present, don't fetch the teams list, as it will fail anyway + if (!authIdToken$.value) return + this.loading$.next(true) const results: GetMyTeamsQuery["myTeams"] = []