fix: queries not waiting for authentication
This commit is contained in:
committed by
liyasthomas
parent
6b8bc618dc
commit
2761894164
@@ -11,16 +11,24 @@ import {
|
|||||||
createClient,
|
createClient,
|
||||||
TypedDocumentNode,
|
TypedDocumentNode,
|
||||||
OperationResult,
|
OperationResult,
|
||||||
defaultExchanges,
|
dedupExchange,
|
||||||
OperationContext,
|
OperationContext,
|
||||||
|
cacheExchange,
|
||||||
|
fetchExchange,
|
||||||
|
makeOperation,
|
||||||
} from "@urql/core"
|
} from "@urql/core"
|
||||||
|
import { authExchange } from "@urql/exchange-auth"
|
||||||
import { devtoolsExchange } from "@urql/devtools"
|
import { devtoolsExchange } from "@urql/devtools"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe, constVoid } from "fp-ts/function"
|
import { pipe, constVoid } from "fp-ts/function"
|
||||||
import { subscribe } from "wonka"
|
import { subscribe } from "wonka"
|
||||||
import clone from "lodash/clone"
|
import clone from "lodash/clone"
|
||||||
import { getAuthIDToken } from "~/helpers/fb/auth"
|
import {
|
||||||
|
getAuthIDToken,
|
||||||
|
probableUser$,
|
||||||
|
waitProbableLoginToConfirm,
|
||||||
|
} from "~/helpers/fb/auth"
|
||||||
|
|
||||||
const BACKEND_GQL_URL =
|
const BACKEND_GQL_URL =
|
||||||
process.env.CONTEXT === "production"
|
process.env.CONTEXT === "production"
|
||||||
@@ -29,16 +37,47 @@ const BACKEND_GQL_URL =
|
|||||||
|
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
url: BACKEND_GQL_URL,
|
url: BACKEND_GQL_URL,
|
||||||
fetchOptions: () => {
|
exchanges: [
|
||||||
const token = getAuthIDToken()
|
devtoolsExchange,
|
||||||
|
dedupExchange,
|
||||||
|
cacheExchange,
|
||||||
|
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 {
|
return {
|
||||||
headers: {
|
authToken: getAuthIDToken(),
|
||||||
authorization: token ? `Bearer ${token}` : "",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exchanges: [devtoolsExchange, ...defaultExchanges],
|
}),
|
||||||
|
fetchExchange,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,7 +157,7 @@ export function useGQLQuery<
|
|||||||
// Take the network error value
|
// Take the network error value
|
||||||
result.error?.networkError,
|
result.error?.networkError,
|
||||||
// If it null, set the left to the generic error name
|
// If it null, set the left to the generic error name
|
||||||
E.fromNullable(result.error?.name),
|
E.fromNullable(result.error?.message),
|
||||||
E.match(
|
E.match(
|
||||||
// The left case (network error was null)
|
// The left case (network error was null)
|
||||||
(gqlErr) =>
|
(gqlErr) =>
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ import {
|
|||||||
Subscription,
|
Subscription,
|
||||||
} from "rxjs"
|
} from "rxjs"
|
||||||
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
|
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
|
||||||
|
import {
|
||||||
|
setLocalConfig,
|
||||||
|
getLocalConfig,
|
||||||
|
removeLocalConfig,
|
||||||
|
} from "~/newstore/localpersistence"
|
||||||
|
|
||||||
export type HoppUser = User & {
|
export type HoppUser = User & {
|
||||||
provider?: string
|
provider?: string
|
||||||
@@ -40,9 +45,10 @@ export type HoppUser = User & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthEvents =
|
type AuthEvents =
|
||||||
| { event: "login"; user: HoppUser }
|
| { event: "probable_login"; user: HoppUser } // We have previous login state, but the app is waiting for authentication
|
||||||
| { event: "logout" }
|
| { event: "login"; user: HoppUser } // We are authenticated
|
||||||
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null }
|
| { 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)
|
* 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>()
|
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
|
* Initializes the firebase authentication related subjects
|
||||||
*/
|
*/
|
||||||
@@ -67,6 +93,17 @@ export function initAuth() {
|
|||||||
|
|
||||||
let extraSnapshotStop: (() => void) | null = null
|
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) => {
|
onAuthStateChanged(auth, (user) => {
|
||||||
/** Whether the user was logged in before */
|
/** Whether the user was logged in before */
|
||||||
const wasLoggedIn = currentUser$.value !== null
|
const wasLoggedIn = currentUser$.value !== null
|
||||||
@@ -135,6 +172,8 @@ export function initAuth() {
|
|||||||
newToken: authIdToken$.value,
|
newToken: authIdToken$.value,
|
||||||
user: currentUser$.value!!, // Force not-null because user is defined
|
user: currentUser$.value!!, // Force not-null because user is defined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setLocalConfig("login_state", JSON.stringify(user))
|
||||||
} else {
|
} else {
|
||||||
authIdToken$.next(null)
|
authIdToken$.next(null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"@nuxtjs/sitemap": "^2.4.0",
|
"@nuxtjs/sitemap": "^2.4.0",
|
||||||
"@nuxtjs/toast": "^3.3.1",
|
"@nuxtjs/toast": "^3.3.1",
|
||||||
"@urql/core": "^2.3.3",
|
"@urql/core": "^2.3.3",
|
||||||
|
"@urql/exchange-auth": "^0.1.6",
|
||||||
"acorn": "^8.5.0",
|
"acorn": "^8.5.0",
|
||||||
"acorn-walk": "^8.2.0",
|
"acorn-walk": "^8.2.0",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
|
|||||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -49,6 +49,7 @@ importers:
|
|||||||
'@types/splitpanes': ^2.2.1
|
'@types/splitpanes': ^2.2.1
|
||||||
'@urql/core': ^2.3.3
|
'@urql/core': ^2.3.3
|
||||||
'@urql/devtools': ^2.0.3
|
'@urql/devtools': ^2.0.3
|
||||||
|
'@urql/exchange-auth': ^0.1.6
|
||||||
'@vue/runtime-dom': ^3.2.20
|
'@vue/runtime-dom': ^3.2.20
|
||||||
'@vue/test-utils': ^1.2.2
|
'@vue/test-utils': ^1.2.2
|
||||||
acorn: ^8.5.0
|
acorn: ^8.5.0
|
||||||
@@ -123,6 +124,7 @@ importers:
|
|||||||
'@nuxtjs/sitemap': 2.4.0
|
'@nuxtjs/sitemap': 2.4.0
|
||||||
'@nuxtjs/toast': 3.3.1
|
'@nuxtjs/toast': 3.3.1
|
||||||
'@urql/core': 2.3.3_graphql@15.7.2
|
'@urql/core': 2.3.3_graphql@15.7.2
|
||||||
|
'@urql/exchange-auth': 0.1.6_graphql@15.7.2
|
||||||
acorn: 8.5.0
|
acorn: 8.5.0
|
||||||
acorn-walk: 8.2.0
|
acorn-walk: 8.2.0
|
||||||
axios: 0.24.0
|
axios: 0.24.0
|
||||||
@@ -4692,6 +4694,16 @@ packages:
|
|||||||
wonka: 4.0.15
|
wonka: 4.0.15
|
||||||
dev: true
|
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:
|
/@vue/babel-helper-vue-jsx-merge-props/1.2.1:
|
||||||
resolution: {integrity: sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==}
|
resolution: {integrity: sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
Reference in New Issue
Block a user