fix: queries not waiting for authentication

This commit is contained in:
Andrew Bastin
2021-10-03 19:12:31 +05:30
committed by liyasthomas
parent 6b8bc618dc
commit 2761894164
4 changed files with 105 additions and 14 deletions

View File

@@ -11,16 +11,24 @@ import {
createClient,
TypedDocumentNode,
OperationResult,
defaultExchanges,
dedupExchange,
OperationContext,
cacheExchange,
fetchExchange,
makeOperation,
} from "@urql/core"
import { authExchange } from "@urql/exchange-auth"
import { devtoolsExchange } from "@urql/devtools"
import * as E from "fp-ts/Either"
import * as TE from "fp-ts/TaskEither"
import { pipe, constVoid } from "fp-ts/function"
import { subscribe } from "wonka"
import clone from "lodash/clone"
import { getAuthIDToken } from "~/helpers/fb/auth"
import {
getAuthIDToken,
probableUser$,
waitProbableLoginToConfirm,
} from "~/helpers/fb/auth"
const BACKEND_GQL_URL =
process.env.CONTEXT === "production"
@@ -29,16 +37,47 @@ const BACKEND_GQL_URL =
const client = createClient({
url: BACKEND_GQL_URL,
fetchOptions: () => {
const token = getAuthIDToken()
exchanges: [
devtoolsExchange,
dedupExchange,
cacheExchange,
authExchange({
addAuthToOperation({ authState, operation }) {
if (!authState || !authState.authToken) {
return operation
}
return {
headers: {
authorization: token ? `Bearer ${token}` : "",
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}`,
},
},
})
},
}
},
exchanges: [devtoolsExchange, ...defaultExchanges],
willAuthError({ authState }) {
return !authState || !authState.authToken
},
getAuth: async () => {
if (!probableUser$.value) return { authToken: null }
await waitProbableLoginToConfirm()
return {
authToken: getAuthIDToken(),
}
},
}),
fetchExchange,
],
})
/**
@@ -118,7 +157,7 @@ export function useGQLQuery<
// Take the network error value
result.error?.networkError,
// If it null, set the left to the generic error name
E.fromNullable(result.error?.name),
E.fromNullable(result.error?.message),
E.match(
// The left case (network error was null)
(gqlErr) =>

View File

@@ -33,6 +33,11 @@ import {
Subscription,
} from "rxjs"
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
import {
setLocalConfig,
getLocalConfig,
removeLocalConfig,
} from "~/newstore/localpersistence"
export type HoppUser = User & {
provider?: string
@@ -40,9 +45,10 @@ export type HoppUser = User & {
}
type AuthEvents =
| { event: "login"; user: HoppUser }
| { event: "logout" }
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null }
| { event: "probable_login"; user: HoppUser } // We have previous login state, but the app is waiting for authentication
| { event: "login"; user: HoppUser } // We are authenticated
| { event: "logout" } // No authentication and we have no previous state
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null } // Token has been updated
/**
* A BehaviorSubject emitting the currently logged in user (or null if not logged in)
@@ -58,6 +64,26 @@ export const authIdToken$ = new BehaviorSubject<string | null>(null)
*/
export const authEvents$ = new Subject<AuthEvents>()
/**
* Like currentUser$ but also gives probable user value
*/
export const probableUser$ = new BehaviorSubject<HoppUser | null>(null)
/**
* Resolves when the probable login resolves into proper login
*/
export const waitProbableLoginToConfirm = () =>
new Promise<void>((resolve, reject) => {
if (authIdToken$.value) resolve()
if (!probableUser$.value) reject(new Error("no_probable_user"))
const sub = authIdToken$.pipe(filter((token) => !!token)).subscribe(() => {
sub?.unsubscribe()
resolve()
})
})
/**
* Initializes the firebase authentication related subjects
*/
@@ -67,6 +93,17 @@ export function initAuth() {
let extraSnapshotStop: (() => void) | null = null
probableUser$.next(JSON.parse(getLocalConfig("login_state") ?? "null"))
currentUser$.subscribe((user) => {
if (user) {
probableUser$.next(user)
} else {
probableUser$.next(null)
removeLocalConfig("login_state")
}
})
onAuthStateChanged(auth, (user) => {
/** Whether the user was logged in before */
const wasLoggedIn = currentUser$.value !== null
@@ -135,6 +172,8 @@ export function initAuth() {
newToken: authIdToken$.value,
user: currentUser$.value!!, // Force not-null because user is defined
})
setLocalConfig("login_state", JSON.stringify(user))
} else {
authIdToken$.next(null)
}

View File

@@ -36,6 +36,7 @@
"@nuxtjs/sitemap": "^2.4.0",
"@nuxtjs/toast": "^3.3.1",
"@urql/core": "^2.3.3",
"@urql/exchange-auth": "^0.1.6",
"acorn": "^8.5.0",
"acorn-walk": "^8.2.0",
"axios": "^0.24.0",

12
pnpm-lock.yaml generated
View File

@@ -49,6 +49,7 @@ importers:
'@types/splitpanes': ^2.2.1
'@urql/core': ^2.3.3
'@urql/devtools': ^2.0.3
'@urql/exchange-auth': ^0.1.6
'@vue/runtime-dom': ^3.2.20
'@vue/test-utils': ^1.2.2
acorn: ^8.5.0
@@ -123,6 +124,7 @@ importers:
'@nuxtjs/sitemap': 2.4.0
'@nuxtjs/toast': 3.3.1
'@urql/core': 2.3.3_graphql@15.7.2
'@urql/exchange-auth': 0.1.6_graphql@15.7.2
acorn: 8.5.0
acorn-walk: 8.2.0
axios: 0.24.0
@@ -4692,6 +4694,16 @@ packages:
wonka: 4.0.15
dev: true
/@urql/exchange-auth/0.1.6_graphql@15.7.2:
resolution: {integrity: sha512-jVyUaV+hHe3p2rIJauh6lgILMAjXOsHQ98xjKhUF3TXYx88TZXuBIl5DPZwnMcGra8YPOSHO/Wsn6NEjO5hQ+Q==}
peerDependencies:
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0
dependencies:
'@urql/core': 2.3.3_graphql@15.7.2
graphql: 15.7.2
wonka: 4.0.15
dev: false
/@vue/babel-helper-vue-jsx-merge-props/1.2.1:
resolution: {integrity: sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==}
dev: false